Université Grenoble alpes.
Version: 14 mars 2020
Didacticiel
Bases de programmation en C++
Déroulement des séances en Licence L3 de physique, 2017-2018
- Suivre le didacticiel Partie I “Bases du langage C++” I, en binome ou seul.
- Lire l'introduction 1, puis passer à la partie I, I.
- Faire tous les exercices demandés. Pour les exercices marqués par (*). Par exemple “tabulation” (*), écrire un programme tabulation.cc et le montrer à l'enseignant (ou montrer plusieurs exercices à la fois).
- On peut seulement lire rapidement les parties appelées “suppléments”.
- Ce didacticiel se termine par la réalisation d'un microprojet en Section 9. Choisir parmi la liste proposée. Rédiger un compte rendu de 2 ou 3 pages avec lyx, avec des images et des formules. Exporter en pdf et en xhtml et le montrer à l'enseignant, avec un oral de 5mn-10mn.
- Optionellement, suivre le didacticiel Partie II “Compléments du langage C++” II.
- Choisir un projet d'expérimentation numérique. Voir la page projets.
- A la dernière séance, chaque étudiant (ou binome) présente son projet sur vidéo projecteur, pendant 15 à 20 mn, à l'ensemble de la classe.
- Cette présentation devra être préparée avec Lyx (qui permet de créer des documents scientifiques) et exportée en html (avec LyxHtml) ou en pdf.
- Pour apprendre Lyx, voici un document en pdf qui sert d'exemple et d'exercice. Voici le même document en html, qui vous servira éventuellement pour copier/coller.
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érifiés par l'enseignant pendant les séances. Il y a de nombreux “liens html” qui renvoie vers plus d'informations.
- Le grand avantage du langage C++ par rapport au langage C est la programmation 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 suffira 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 A, B et C appartenant à cette classe et écrire A=B+C ou n'importe quelle autre expression mathématique matricielle.
- Pourquoi enseigner le C++ à l'université, dans la filiè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 scientifique. 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 scientifique. 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 (fichier exécutable) 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, C++17, C++20 ont pour vocation de rendre le C++ plus facile à apprendre et toujours performant.
- Références:
- 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 afficher un nombre avec une certaine précision numérique, si vous ne savez pas comment faire, tapez quelques mots clef comme “affichage 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” ce qui vous amène à une page de documentation.
- 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
- Un MOOC sur Linux. Un MOOC sur le C++. Un autre MOOC sur le C++.
- Sur ce site on peut ecrire, compiler et executer un programme C++ en ligne.
- Livre pour le langage C++:
- Stroustrup-A Tour of C++-Addison-Wesley (2018)
- 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 de TP 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, voir ci-dessous.
Dans ce didacticiel, nous utiliserons les logiciels et librairies suivantes qui sont en général téléchargeables depuis votre distribution:
1.1.1 Installer Xubuntu et l'environnement de programmation c++ sur son ordinateur
- Si ubuntu (linux) est déjà installé sur votre ordinateur, il faut installer les librairies requises pour ce didacticiel. Pour cela, ouvrir un terminal et écrire, l'une après l'autre les lignes suivantes:
sudo apt-get install -y root-system # librairie Root du Cern
sudo apt-get install -y emacs emacs-goodies-el global clang auto-complete-el cscope-el # logiciel pour recherche de fonctions etc
sudo apt-get install -y libarmadillo-dev # C++ linear algebra library
sudo apt-get install -y libboost-dev
sudo apt-get install -y libboost-all-dev
- Autre info:.
1.1.2 Installation sur Mac Os
brew install armadillo
brew install root
brew install emacs-clang-complete-async
brew cask install emacs
brew install boost
brew install xfig
brew install fig2dev
1.1.3 Installation sur windows
1.2 Environnement de programmation
Avec un petit exemple nous allons voir comment:
- écrire le code d'un programme en C++
- le compiler, c'est à dire que l'ordinateur traduit le programme C++ en un programme “exécutable” (en langage machine) qu'il pourra exécuter ensuite.
- exécuter le programme
- éventuellement de le “débugger” (cela signifie recherche d'erreurs): si le programme ne fonctionne pas comme on le désire, on l'exécute instruction après instruction afin de suivre ce qu'il fait pas à pas, dans le but de trouver un “bug” (= une “erreur”).
Dans la Section suivante nous effectuons ces taches avec l'éditeur
emacs et une fenêtre de commandes (terminal).
1.2.1 Utilisation de commandes dans un terminal
Si vous avez une question sur l'utilisation de ubuntu, par exemple comment ouvrir et utiliser un terminal, écrire dans google: “ubuntu terminal”.
Dans ubuntu, ouvrir une fenêtre de commandes (ou terminal). Dans cette fenêtre de commandes, vous pouvez écrire des commandes à l'ordinateur. Ces commandes sont dans le langage UNIX, système avec lequel fonctionne l'ordinateur. Cette section vous explique les principales commandes de UNIX qu'il faut connaître: les commandes qui permettent de gérer l'emplacement et le nom de vos fichiers (qui seront par exemples vos programmes en C++).
Il est important de savoir que les fichiers sont classés dans l'ordinateur (sur le disque magnétique) selon une structure arborescente. Chaque noeud de l'arborescence s'appelle un répertoire ou dossier ou directory (en anglais). Dans ces répertoires se trouvent des fichiers (ou file en anglais).
Par exemple /h/moore/u4/phyliphrec/aaal/toto.cc signifie que le fichier toto.cc se trouve dans le répertoire aaal qui se trouve lui même dans le répertoire phyliphrec, qui se trouve lui même dans ...
1.2.1.1 Les répertoires
- Pour connaître le répertoire de votre fenêtre de commande (le répertoire courant): pwd
sur la figure suivante par exemple, le répertoire courant est /home/faure
- Pour afficher la liste des fichiers et des sous répertoires du répertoire courant: ls ou ls -als (pour avoir plus de détails)
- Pour changer de répertoire:
- pour aller dans le sous répertoire appelé rep: cd rep
- pour aller dans le répertoire parent: cd ..
- pour revenir à votre répertoire d'origine: cd ou cd ~
- Pour créer un sous répertoire appelé rep: mkdir rep
- Pour détruire un sous répertoire appelé rep: rm -r rep
1.2.1.2 Exercices sur les répertoires
A l'aide des commandes expliquées ci-dessus:
- Placez vous dans votre répertoire d'origine: cd
- Vérifiez que vous y êtes bien: pwd
- Regardez la liste des fichiers par: ls , puis avec: ls -als
- Créer un répertoire du nom: essai Vérifier son existence (avec ls).
- Aller dans ce répertoire: cd essai Vérifier que vous y êtes (avec pwd).
- Revenir dans le répertoire d'origine par cd .. Vérifiez que vous y etes. Détruire le répertoire essai
1.2.1.3 Les fichiers (en exercice)
- Créer un nouveau fichier avec l'éditeur emacs: emacs fichier.txt &
écrire le texte: salut
Menu: File/Save
Menu: File/Quit
On vérifie le contenu du fichier dans la fenêtre de commandes: more fichier.txt
- Pour changer le nom du fichier.txt en fichier2.txt: mv fichier.txt fichier2.txt
- Pour déplacer le fichier2.txt dans le sous répertoire rep:
mkdir rep
mv fichier2.txt rep/fichier3.txt
- Pour copier le fichier3.txt dans le répertoire parent:
cd rep
cp fichier3.txt ../fichier1.txt
- Pour détruire le(s) fichiers:
rm fichier3.txt
cd ..
rm fichier1.txt
rm -r rep
1.2.1.4 La documentation sur les commandes unix
- Pour avoir la documentation sur la commande ls par exemple, taper dans une fenetre de commande: man ls
- Voici une documentation sur les commandes possibles bash pour la fenêtre terminal.
1.2.2
Programmer avec l'éditeur emacs et une fenêtre de commandes
- Dans ubuntu, ouvrir une fenêtre de commandes (ou terminal). Se placer dans le bon répertoire de travail. Pour cela comme expliqué dans la section précédente, utiliser les commandes pwd (qui dit dans quel dossier on est situé), ls (qui liste les fichiers et dossiers) , cd dossier (: qui passe au dossier indiqué), cd .. (qui passe au dossier parent), cd . (qui reste dans le même dossier, c'est inutile), cd (qui revient au dossier de départ),
- Ecrire la commande emacs & qui lance l'éditeur. Le signe & signifie que le terminal 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”.
- Dans emacs, ouvrir un nouveau fichier projet1.cc qui sera vide la première fois (pour faire cela: Menu/File/Visit New File et ecrire projet1.cc , Valider). Le suffixe .cc signifie qu'il contiendra du code C++ (ou peut aussi choisir le suffixe .cpp). Dans ce fichier, Copier/coller le code suivant:
#include <iostream>
using namespace std;
int main()
{
cout << "Bonjour!" << endl;
}
et sauvegarder ce fichier qui contient le code C++ de votre programme. (Observer les raccourcis clavier proposés par emacs dans ses menus pour toutes ces commandes: par exemple “Save : C-x C-s” signifie que les touches Ctrl avec x puis Ctrl avec s. “Shell Command M-!” signifie les touches Alt avec !).
- Dans emacs ouvrir un nouveau fichier Makefile qui sera vide la première fois. Dans ce fichierCopier/coller le code de compilation suivant:
all:
g++ projet1.cc -o projet1 -std=c++11 -O3 -Wall -lm
et sauvegarder ce fichier qui contient les instructions pour compiler votre programme. Attention: au début de la deuxième ligne rajouter un espace de tabulation avant g++. Remarque: on peut basculer d'un fichier à l'autre dans emacs par le menu “Buffers” ou par le clavier avec C-x C-.
- Compiler le programme par la commande du menu: Tools/Compile.. qui fait apparaitre en bas de page: make -k que l'on remplace par make all (puis faire entrée). Si tout se passe bien le dernier message est “compilation finished”.
- Remarques: cela a pour effet d'exécuter la commande de compilation qui se trouve dans le fichier Makefile: elle dit qu'il faut utiliser le compilateur g++ et qu'à partir du fichier projet1.cc il faut créer en sortie (-o = “output”) un fichier exécutable appelé projet1. On a rajouté en option (inutiles dans cet exemple) -std=c++11 qui signifie que on utilise la syntaxe du c++11 et -lm qui signifie que on l'on souhaite utiliser la librairie mathématique. L'option -Wall signifie que le compilateur montrera “all the Warnings” , c'est à dire toutes les imperfections dans la syntaxe.
- Dans le terminal, placez vous dans le répertoire qui contient votre projet, vérifiez avec la commande ls qu'il contient le fichier exécutable projet1 , et exécutez ce fichier en écrivant: ./projet1
(remarque: ./ signifie le répertoire présent)
Exercice 1.2.1.
- Dans le programme projet1.cc ajoutez une erreur, par exemple en enlevant un signe ; en fin 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.
- Dans les instructions ci-dessus nous vous avons proposé d'utiliser un fichier Makefile qui sera surtout utile pour les projets de programmation 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 -O3 -Wall -lm
le désavantage est que pour corriger l'erreur il faut soit même se placer sur la ligne indiquée.
Une autre possibilité pour compiler est d'écrire la commande make all dans le terminal qui a pour effet d'exécuter le fichier Makefile.
- emacs peut mettre votre programme “en forme”: pour cela sélectionnez tout le texte et appuyez sur la touche tabulation. Il apparaît des décalages de lignes convenables, mais non obligatoires.
- Remarque: pour écrire le programme, à la place de emacs vous pouvez utiliser tout autre éditeur de texte.
1.2.2.1 Paramétrage de emacs
Voici quelques paramétrages utiles pour la suite:
- En haut du fichier .cc rajouter la ligne suivante:
// -*- mode:C++ ; compile-command: "make all" -*-
Cela a pour effet de préciser la commande de compilation lorsque l'on fait Menu/Tools/Compile
On peut aussi remplacer "make all" par "make all; projet1;" ce qui a pour effet de compiler et de lancer l'exécutable projet1
- Dans le fichier ~/.emacs rajouter la ligne:
(global-set-key (kbd "<f9>") 'compile)
Ainsi il suffira dans emacs d'utiliser la touche f9 pour lancer la commande de compilation.
- Autres options, voir developper en c++ sous emacs. Raccourcis claviers sous emacs. Manuel de Emacs
Partie I
Bases du langage C++
Chapitre 2 Le tout début. Lire le clavier et afficher à l'écran
2.1 Affichage dans un terminal
Voici un petit programme en C++ qui additionne deux nombres entiers a et b , affecte le résultat dans c et affiche le résultat à l'écran.
#include <iostream>
using namespace std;
/* ==============
Programme principal
===================*/
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;
}
Exercice 2.1.1.
Recopiez ce programme dans un fichier prog.cc , modifier la commande de compilation de Makefile pour le compiler et exécutez le.
Résultat du programme:
la somme de 1 et 2 vaut 3
Exercice 2.1.4.
“tabulation” (*) "t" permet d'afficher une tabulation (i.e. sauter un espace jusqu'à la colonne suivante). Modifier le programme précédent pour afficher:
objets:a b c=a+b
valeurs: 1 2 3
2.2 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()
{
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 2.2.1.
Recopier ce programme et exécuter le.
Chapitre 3 Déclaration et affectation des objets
3.1 Objets de base
3.1.1 Les déclarations d'objets de base
dans un programme, pour stocker une information, 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, etc).
Voici quelques classes de base qui existent en C++. Il y en a d'autres, voir
variables:
Déclaration: classe objet;
|
signification
|
Limites
|
int a;
|
nombre entier
|
-2147483648 à 2147483647
|
double d;
|
nombre réel double précision
|
à près
|
char e;
|
caractère
|
|
bool v;
|
booléen: vrai (true) ou faux (false)
|
|
char * t;
|
chaîne de caractères
|
|
3.1.2 Initialisation d'un objet de base
On peut déclarer un objet et l'initialiser en même temps de différentes manières. Voici un exemple.
#include <iostream>
using namespace std;
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;
}
}
3.1.2.1 Remarques
- i est un int qui vaut 0, x un float qui vaut 1.23, x1 est un double qui vaut 3.4, etc..
- Le terme auto signifie que le compilateur devine lui même la classe de l'objet, ici z est un double.
- Dans le nom des objets, le langage C++ fait la différence entre les majuscules et les minuscules. On peut choisir ce que l'on veut et utiliser même des chiffres et le caractère _.
- On peut initialiser un objet avec des paranthèses comme x1(3.4). C'est équivalent que d'écrire x1=3.4. On peut initialiser un objet avec un objet déjà existant comme y(x1) ou y2 = x2.
- Un caractère est entre le signe ', comme 'X'.
- Un texte (chaîne de caractère) est entre le signe ". Ici l'objet texte est du type const char * qui signifie que c'est une “chaine de caractères” (ou tableau de caractères) constante. Les indices du tableau commencent à 0. C'est pourquoi texte[0] extrait le premier caractère qui est q. On expliquera plus tard que texte est en fait un pointeur sur charactères.
- A la dernière ligne on déclare dans un bloc d'accolades ou bloc d'instructions {..}. Cela est possible et signifie que dans ce bloc x est une nouvelle variable locale (qui masque la valeur de x précédente).
- On pourra bien sûr modifier ces valeurs dans la suite du programme.
Exercice 3.1.2.
“accolades” (*) Recopiez le programme, modifiez le pour qu'il affiche la valeur de chaque objet, et exécuter le. (si “auto” signale une erreur, vérifiez si l'option c++11 est cochée, voir remarque de l'exercice
10.1.2(4).) Afficher la valeur de x dans le bloc d'accolades qui contient
double x=5; et après ce bloc d'accolades. Dans un deuxième temps, essayer de ne pas mettre d'accolades et comprendre l'erreur de compilation.
3.1.3 Affichage 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>
//..................
main ()
{
double c = M_PI;
cout<<setprecision(3)<<c<<endl;
}
Résultat:
3.14
3.1.4 Attention à la division Euclidienne
Voici un exemple:
#include <iostream>
using namespace std;
//......
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
Remarques:
- L'exemple précédent montre que l'écriture 7/2 signifie le quotient de la division euclidenne qui est bien 3 car 7=2*3+1. Le reste 1 euclidien est obtenu par l'opération 7%2 qui signifie 7 modulo 2.
- Pour effectuer la division parmi les nombres réels (à virgule) il faut utiliser le type double.
- L'instruction (double)a permet de convertir la variable a qui est de classe int en une variable de classe double. Ce changement de classe est appelé “cast”.
- Attention, cette convention que 7/2 est la division euclidienne en langage C et C++ est souvent source d'erreur.
3.1.5
Pour générer un nombre entier p aléatoire
#include <iostream>
using namespace std;
#include <time.h>
#include <stdlib.h>
main ()
{
srand(time(NULL)); // initialise le hasard.(faire cela une seule fois en debut de programme)
int N = 100;
int p = rand()%(N+1); // -> entier aléatoire entre 0 et N compris.
cout<<" p="<<p<<endl;
}
3.1.6 Fonctions mathématiques
Références
3.2 Objets plus élaborés de la classe standard
Nous présentons ici quelques classes plus élaborées que les classes de base et très utiles. Pour une liste complète des classes d'objets possibles en C++, dans la librairie standard (STL, Standard Template Library) voir par exemple la
référence.
3.2.1 Chaines de caractères (string)
Référence:
string. Les chaines de caractères permettent de manipuler des suites de caractères, comme des phrases.
Voici un exemple de programme que vous pouvez essayer.
#include <iostream>
#include <string>
using namespace std;
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
Exercice 3.2.2.
“texte” (*) Modifier le programme précédent pour qu'il affiche:
J'aime le café sans sucre
Le troisième caractère du texte précédent est:a
3.2.1.1
Conversion de chiffres en chaine de caractère et inversement. Solution simple.
Avec les fonctions
stoi (penser “
String
to Integer”)
stod (penser “
String
to Double”) et
to_string. On souhaite par exemple construire une chaine de caractères qui contient le résultat d'une opération numérique.
#include <iostream>
using namespace std;
#include <string>
#include <sstream>
//.....
main ()
{
//..... Conversion number to string.
int d=7; // chiffre
string S = to_string(d);
cout<<"d="<<d<<" S="<<S<<endl;
//..... Conversion string to number.
string s5 = "3"; // chaine contenant des chiffres
int i5 = stoi(s5);
string s6 = "3.14"; // chaine contenant des chiffres
double d6 = stod(s6);
cout<<"i5="<<i5<<" d6="<<d6<<endl;
}
Résultat:
d=7 S=7
i5=3 d6=3.14
3.2.2 Nombres complexes
#include <iostream>
using namespace std;
#include <complex>
typedef complex<double> Complex;
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
- L'écriture 2. est équivalente à 2.0 ou à 2. Ainsi a=2.+I*4. est équivalente à a=2+4*I
- typedef complex<double> Complex; signifie que le mot Complex est un raccourci pour le type complex<double> qui est un nombre complexe avec double en partie réelle et imaginaire.
3.2.3 Liste d'objets (ou tableau de dimension 1): 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). Voici les informations sur la classe
vector.
Par exemple pour une liste de nombres entiers avec vector<int>:
#include <iostream>
using namespace std;
#include <vector>
main ()
{
vector<int> L; // liste vide qui contiendra des objets de type int
L.push_back(2); //rajoute l'element 2 a la fin de la liste
L.push_back(3); // rajoute 3 a la fin de la liste
L.push_back(5);
cout<<"La liste a la taille :"<<L.size()<<endl;
cout<<"Ses elements sont: "<<L[0]<<" , "<<L[1]<<" , "<<L[2]<<endl;
}
Résultat:
La liste a la taille :3
Ses elements sont: 2 , 3 , 5
Autre exemple:
#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>
main ()
{
//--- recherche la position de l'element maximal de la liste
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;
int p = max_element(L.begin(),L.end())-L.begin(); //renvoit la position du maximum dans l'ordre (0,1,2..)
cout<<"L'élement le plus grand est "<<L[p]<<endl;
cout<<"Il est à la position "<<p<<endl;
//--- cherche la premiere/derniere position d'un element avec find() / rfind()
int e = 6;
int p2 = find(L.begin(), L.end(), e) - L.begin(); // cherche la position de l'element e
if(p2<L.size())
cout<<"Le "<<e<<" est à la position "<<p2<<endl;
else
cout<<"Le "<<e<<" n'est pas dans la liste."<<endl;
}
Résultat:
Le premier élément de la liste est 5
L'élement le plus grand est 34
Il est à la position 4
Le 6 est à la position 5
3.2.3.1 Poser un objet sans copier:
3.3
Vecteurs et matrices pour l'algèbre linéaire avec la librairie Armadillo
Commande de compilation:
Cette librairie ne fait pas partie de la "
librairie standard STL”, (si vous utilisez votre propre ordinateur, il faut donc l'installer au préalable). Il faut
rajouter -larmadillo dans les options de compilation: si vous utilisez un Makefile c'est à la suite de
-lm dans la ligne de compilation (
1). Si vous utilisez codeblocks, c'est à rajouter dans Menu/Settings/Compiler/LinkerSettings/OtherLinkerOptions).
Exemple:
#include <iostream>
using namespace std;
#include <armadillo>
using namespace arma;
main ()
{
Col<int> V("1 2 3"); // vecteur colonne d'entiers
cout<<"V="<<endl<<V<<endl;
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
Exercice 3.3.2.
Recopiez ce programme et exécuter le.
Exercice 3.3.3.
“matrices” (*) Modifiez le programme précédent pour qu'il calcule et affiche:
A = 1 1
0 1
B = 1 0
1 1
A*B = 2 1
1 1
3.3.1 Quelques fonctions utiles sur les vecteurs et matrices
Veuillez consulter la page
documentation pour toutes les fonctionnalités de armadillo sur les vecteurs et matrices.
Voici quelques exemples utiles.
#include <iostream>
using namespace std;
#include <armadillo>
using namespace arma;
main ()
{
//---- initialisations
//... vec est equivalent a Col<double>
vec V1 = randu<vec>(5); // vecteur colonne de 10 composantes aleatoires unif. dans [0,1]
cout<<"V1="<<endl<<V1<<endl;
//... mat est equivalent a Mat<double>
mat M1;
M1.zeros(3,3); // matrice 3*3 remplie de zeros
cout<<"M1="<<endl<<M1<<endl;
//----- conversions de types
vector<double> V2 = conv_to<vector<double>>::from(V1); // V1 -> V2
cout<<"V2[0]="<<V2[0]<<endl;
}
Résultat
V1=
0.7868
0.2505
0.7107
0.9467
0.0193
M1=
0 0 0
0 0 0
0 0 0
V2[0]=0.786821
3.4
Objets graphiques avec la librairie Root
Comme il est très utile et agréable de représenter des données et des 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 graphisme simple (lignes, points, ronds,..)
- de traiter des 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. Il y a aussi Documentation générale. 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 suffit 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.
- Si “root” n'est pas installé sur votre ordinateur, pour l'installer sous (X)Ubuntu, il faut écrire dans un terminal:
sudo apt-get install -y xorg-dev
sudo apt-get install x-dev
sudo apt-get install root-system
3.4.1
Commande de compilation
- Si vous utilisez un Makefile, il faut rajouter à la ligne de compilation (1), à la suite de -lm, les instructions suivantes:
-I/usr/include/root `root-config --cflags` `root-config --libs` `root-config --glibs`
- 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:
`root-config --cflags` `root-config --libs` `root-config --glibs`
3.4.2
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()
{
TApplication theApp("App", nullptr, nullptr); // (A)
TCanvas c("c","fenetre",400,400); // (B) objet fenetre graphique. Taille en pixels
c.Range(0,0,5,5); // coordonnees de la fenetre c: x:0-5, y:0-5 (optionnel, par defaut ce sera 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) garde la fenetre ouverte et permet a l'utilisateur d'interagir.
}
Résultat du programme:
Exercice 3.4.3.
- 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 affiche la position de la souris.
- Modifier le programme afin d'afficher un cercle rouge au centre de la fenetre. Pour cela, rajouter la commande e.SetLineColor(kRed); à la bonne place. (voici la table des couleurs: table des couleurs).
Exercice 3.4.5.
Modifier 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
3.4.3 Quelques objets graphiques
Voici quelques exemples de
graphisme 2D. Remarquez que au début du programme on ajoute une ligne spécifique par exemple
#include <TLine.h> si on utilise la classe TLine.
#include <TApplication.h>
#include <TCanvas.h>
#include <TLine.h>
#include <TEllipse.h>
#include <TMarker.h>
#include <TBox.h>
#include <TText.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//.. La fenetre
TCanvas c("titre","titre",10,10,400,400); //position x,y sur l'ecran et taille X,Y en pixels
c.Range(0,0,1,1); // xmin,ymin,xmax,ymax, systeme de coordonnees
//.. une ligne bleue
TLine l(0.1,0.5,0.3,0.9); // x1,y1,x2,y2.
l.SetLineColor(kBlue); // bleu
l.Draw();
//.. Une Ellipse (ou cercle)
TEllipse e(0.2,0.3,0.1); // (centre (x,y) et rayon r
e.Draw();
//.. Un point
TMarker m(0.3,0.4,8); // (x,y) et 6 ou 8: forme du Marker
m.SetMarkerColor(kPink);
m.Draw();
//.. Un rectangle vert
TBox p(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(0.2,0.2,"un petit texte"); // position x,y
texte.Draw();
c.Update(); // Montre le dessin
theApp.Run(); // garde la fenetre ouverte et permet a l'utilisateur d'interagir.
}
3.5 Suppléments
3.5.1 Continuer une ligne de c++ à la ligne suivante avec \
#include <iostream>
using namespace std;
main ()
{
cout<<”abcdefghij”<<endl;
cout<<”abc\
def\
ghij”\
<<endl;
}
résultat:
abcdefghij
abcdefghij
3.5.2 Sur les types
3.5.2.1 Renommage
Dans la déclaration
vector<int> L; on dit que l'objet
L est du type
vector<int>. Parfois un type peut être long à écrire. Pour simplifier les notations, voici deux commandes qui sont équivalentes et qui ont pour effet de définir le nouveau type
C comme étant équivalent au type
vector<int> (déjà existant).
Reference.
#include <vector>
using C = vector<int>;
typedef vector<int> C;
3.5.2.2
Connaitre le type
pour connaitre le type de la variable
a on peut utiliser
typeid(a).name() et écrire
#include <iostream>
using namespace std;
#include <typeinfo>
main ()
{
auto a='A';
cout << typeid(a).name() <<endl;
auto x=3.14;
cout << typeid(x).name() <<endl;
auto s="ABC";
cout << typeid(s).name() <<endl;
}
résultat:
c
d
PKc
Dans ce résultat, c signifie caractère, d signifie double et PKc signifie chaine de caractères.
3.5.3 Quelques opérations élémentaires
#include <iostream>
using namespace std;
main ()
{
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;
}
résultat
b=1
b=2
b=1
3.5.4 Sur les chaines de caractères
3.5.4.1 Manipulations. Recherche dans une chaine, extraction ...
#include <iostream>
using namespace std;
#include <string>
int main()
{
//----- find() ---------
string s1 = "ab&-e&-f";
cout<<"chaine s1="<<s1<<endl;
auto pos1 = s1.find("&-"); // recherche premiere occurence de "&-" dans s1
if(pos1!=string::npos)
cout<<"Dans la chaine s1, \"&-\" se trouve a la position pos1 = "<<pos1<<endl;
auto pos2 = s1.find("toto");
if(pos2==string::npos)
cout<<"Dans la chaine s1, \"toto\" n'a pas été trouvé"<<endl;
//----- substr() ---------
string s2 = s1.substr(0, s1.find("&-")); // extrait sous chaine entre le debut (indice 0) la premiere occurence de "&-"
string s3 = s1.substr(0, s1.rfind("&-")); // extrait sous chaine entre le debut (indice 0) et la derniere occurence de "&-"
string s7 = s1.substr(s1.find("&-")); // extrait sous chaine entre la premiere occurence de "&-" et la fin
string s4 = s1; // copie
s4.erase(0,2); // a la position 0, enleve 2 caracteres
string s5 = s1; // copie
s5.erase(s1.size()-2,2); // enleve les 2 caracteres de la fin
string s6 = s1; // copie
int p;
while(((p=s6.find("-"))>=0) && (p<=s6.size()))
s6.replace(p,1,"*"); // remplace tous les "-" par "*"
cout<<"s2="<<s2<<endl
<<"s3="<<s3<<endl
<<"s7="<<s7<<endl
<<"s4="<<s4<<endl
<<"s5="<<s5<<endl
<<"s6="<<s6<<endl;
}
Résultat:
chaine s1=ab&-e&-f
Dans la chaine s1, "&-" se trouve a la position pos1 = 2
Dans la chaine s1, "toto" n'a pas été trouvé
s2=ab
s3=ab&-e
s7=&-e&-f
s4=&-e&-f
s5=ab&-e&
s6=ab&*e&*f
3.5.4.2 Conversion de chiffres en chaine de caractère et inversement. Solution sophistiquée (permet des mélanges de type)
On a déjà un moyen élémentaire en Section
3.2.1.1.
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. Inversement pour extraire des chiffres d'une chaine de caractère il faut utiliser la classe istringstream .
#include <iostream>
using namespace std;
#include <string>
#include <sstream>
//.....
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= "<<s<<endl; // affichage pour verifier
//..... Conversion string -> chiffres.
string s2 = "3 4"; // chaine contenant des chiffres
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<<"s2="<<s2<<endl;
cout<<"a="<<a<<" b="<<b<<endl; // affichage pour verifier
}
Résultat:
d=7 S=7
s= resultat :7
s2=3 4
a=3 b=4
3.5.4.3 Conversion nombre <-> chaine de caractere qui représente le nombre en base 2
#include <iostream>
using namespace std;
#include <string>
#include <bitset>
//.....
main ()
{
//... Conversion entier -> string (base 2)
int n4=117;
string s4 = bitset<8>(n4).to_string(); // nombre -> string (base 2)
cout<<"n4="<<n4<<" s4="<<s4<<endl;
//.. Conversion string (nombre en base 2) -> entier
string s5="10011";
unsigned long n5 = bitset<8>(s5).to_ulong();
cout<<"s5="<<s5<<" n5="<<n5<<endl;
}
Resultat:
n4=117 s4=01110101
s5=10011 n5=19
3.5.4.4 Conversion de string vers char*
Il est parfois utile d'obtenir la chaine sous le format char*.
3.5.5 Quelques opérations en base 2 (binaire, les bits) et en base 16 (hexadecimale)
#include <iostream>
using namespace std;
#include <bitset>
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;
cout<<" Nombre de bits non nuls de A="<< bitset<8>(A).count()<<endl;
//.. decalages de bits
int b= A<<4; // equivalent: b= A * pow(2,4)
cout<<" b ="<<b<<" = "<< bitset<8>(b)<<endl;
int c = b>>3; // equivalent: c= b / pow(2,3)
cout<<" c ="<<c<<" = "<< bitset<8>(c)<<endl;
//.. operations bits a bits
int B = 0b1;
int d = A | B; // ou inclusif bit a bit cad 1|1 donne 1
int e = A ^ B; // ou exclusif bit a bit cad 1^1 donne 0
int f = A & B; // et bit a bit
int g = ~A; // non A, inversion des bits
cout<<" B="<<B<<" = "<<bitset<4>(B)<<endl;
cout<<" A|B = "<<d<<" = "<<bitset<4>(d)<<endl;
cout<<" A^B = "<<e<<" = "<<bitset<4>(e)<<endl;
cout<<" A&B = "<<f<<" = "<<bitset<4>(f)<<endl;
cout<<" ~A = "<<g<<" = "<<bitset<8>(g)<<endl;
}
Resultat:
A=9 en binaire = 00001001
Nombre de bits non nuls de A=2
b =144 = 10010000
c =18 = 00010010
B=1 = 0001
A|B = 9 = 1001
A^B = 8 = 1000
A&B = 1 = 0001
~A = -10 = 11110110
3.5.6 tuple : ensemble d'objets divers
Un objet de la classe
tuple est un ensemble d'objets divers. Par exemple:
#include <iostream>
using namespace std;
#include <tuple>
main ()
{
//... construction
tuple<int,char> o1(10,'x'); // creation d'un objet constitué d'un entier 10 et un caractère x
auto o2 = make_tuple ("test", 3.1, 14, 'y'); // construction sans preciser les types
//... lecture des elements
int a = get<0>(o1); // on extrait le premier element (position 0)
cout<<"a="<<a<<endl;
int b; char c;
tie (b,c) = o1; // extraits les elements de o1
cout<<"b="<<b<<" c="<<c<<endl;
int d;
tie (ignore, ignore, d, ignore) = o2; // extrait certains elements de o2
cout<<"d="<<d<<endl;
//..... ecriture d'elements
get<0>(o1) = 100; // on remplace l'element 0 de o1
cout<<"o1[0]="<<get<0>(o1)<<endl;
//.. concatenation de tuples
auto o3 = tuple_cat(o1,o2);
cout<<"o3[5]="<<get<5>(o3)<<endl;
}
Résultat:
a=10
b=10 c=x
d=14
o1[0]=100
o3[5]=y
3.5.7 Vecteurs et matrices avec la librairie armadillo
3.5.7.1 Diagonalisation d'une matrice symétrique réelle
#include <iostream>
using namespace std;
#include <armadillo>
using namespace arma;
main ()
{
int N=3;
mat M = randu<mat>(N,N); // elements au hasard
M= 0.5*(M + trans(M)); // la rend symetrique
vec val_p;
mat vec_p;
eig_sym( val_p, vec_p, M);
cout<<"Matrice symetrique M="<<endl<<M<<endl;
cout<<"valeurs propres="<<endl<<val_p<<endl;
cout<<"vecteurs propres en colonnes="<<endl<<vec_p<<endl;
cout<<"Verification M v0-l0 *v0 = 0 ? on trouve: "<<endl<<M*vec_p.col(0) - val_p(0) *vec_p.col(0) <<endl;
}
Résultat:
Matrice symetrique M=
0.7868 0.5986 0.4810
0.5986 0.0193 0.2138
0.4810 0.2138 0.5206
valeurs propres=
-0.3109
0.2173
1.4203
vecteurs propres en colonnes=
0.5003 0.4080 0.7637
-0.8633 0.3037 0.4032
-0.0674 -0.8610 0.5041
Verification M v0-l0 *v0 = 0 ? on trouve:
-2.2204e-16
-2.2204e-16
-1.2143e-16
3.5.7.2 Diagonalisation d'une matrice hermitienne complexe
#include <iostream>
using namespace std;
#include <armadillo>
using namespace arma;
main ()
{
int N=3;
cx_mat M = randu<cx_mat>(N,N); // elements au hasard
M= 0.5*(M + trans(M)); // la rend symetrique
vec val_p;
cx_mat vec_p;
eig_sym( val_p, vec_p, M);
cout<<"Matrice symetrique M="<<endl<<M<<endl;
cout<<"valeurs propres="<<endl<<val_p<<endl;
cout<<"vecteurs propres en colonnes="<<endl<<vec_p<<endl;
cout<<"Verification M v0-l0 *v0 = 0 ? on trouve: "<<endl<<M*vec_p.col(0) - val_p(0) *vec_p.col(0) <<endl;
}
Résultat:
Matrice symetrique M=
(+7.868e-01,+0.000e+00) (+4.810e-01,-4.620e-01) (+7.966e-02,+6.948e-02)
(+4.810e-01,+4.620e-01) (+5.206e-01,+0.000e+00) (+3.981e-01,+1.480e-01)
(+7.966e-02,-6.948e-02) (+3.981e-01,-1.480e-01) (+4.998e-01,+0.000e+00)
valeurs propres=
-0.1786
0.5407
1.4451
vecteurs propres en colonnes=
(+3.937e-01,-3.110e-01) (+4.834e-01,-2.058e-01) (+6.801e-01,-9.884e-02)
(-7.290e-01,-1.402e-01) (-1.696e-01,+6.247e-02) (+5.471e-01,+3.419e-01)
(+4.440e-01,+0.000e+00) (-8.315e-01,+0.000e+00) (+3.339e-01,-0.000e+00)
Verification M v0-l0 *v0 = 0 ? on trouve:
(-8.327e-17,+5.551e-17)
(-5.551e-17,+6.939e-18)
(-1.804e-16,+1.388e-17)
3.5.7.3 Diagonalisation d'une matrice complexe quelconque
#include <iostream>
using namespace std;
#include <armadillo>
using namespace arma;
main ()
{
int N=3;
cx_mat M = randu<cx_mat>(N,N); // elements au hasard
cx_vec val_p;
cx_mat vec_p;
eig_gen( val_p, vec_p, M); // diagonalise
//... ordonne les valeurs propres (et vect p) par module decroissant
uvec indices = sort_index(abs(val_p),"descend"); // liste des indices, "ascend" or "descend"
val_p=val_p(indices); // vecteur ordonné
vect_p=vect_p.cols(indices); // vecteur ordonné
cout<<"Matrice complexe M="<<endl<<M<<endl;
cout<<"valeurs propres="<<endl<<val_p<<endl;
cout<<"vecteurs propres en colonnes="<<endl<<vec_p<<endl;
cout<<"Verification: M v0-l0 *v0 = 0 ? on trouve: "<<endl<<M*vec_p.col(0) - val_p(0) *vec_p.col(0) <<endl;
}
3.5.7.4 Matrices creuses
#include <iostream>
using namespace std;
#include <armadillo>
using namespace arma;
main ()
{
int N=10;
//---- creation de matrice sparse
sp_mat A = sprandu<sp_mat>(N, N, 0.1); // matrice sparse N*N aleatoire avec une densite 0.1 d'elements non nuls
sp_mat B(N,N);
B(1,2)=1;
cout<<"Matrice B="<<endl<<B<<endl; // affiche que les elements non nuls
sp_mat M = A.t()*A;
M(1,1)=1;
cout<<"Matrice symetrique M="<<endl<<M<<endl; // affiche que les elements non nuls
//---------- diagonalisation
vec val_p;
mat vec_p;
int n=5;
eigs_sym(val_p, vec_p, M, n, "lm"); // chercher les n premieres eigenvalues/eigenvectors avec option: "lm": les + grandes (default), ou "sm": les + petites.
cout<<"valeurs propres="<<endl<<val_p<<endl;
cout<<"vecteurs propres en colonnes="<<endl<<vec_p<<endl;
cout<<"Verification M v0-l0 *v0 = 0 ? on trouve: "<<endl<<M*vec_p.col(0) - val_p(0) *vec_p.col(0) <<endl;
}
Chapitre 4 Les instructions de base
4.1 La boucle "for"
L'instruction for permet de répéter un bloc d'instructions. Par exemple, la syntaxe de la ligne for(int i=2; i<=8; i=i+2) dans l'exemple ci-dessous signifie: “Fait les instructions en partant de l'entier 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)”.
#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;
}
}
résultat:
2 4
4 16
6 36
8 64
Exercice 4.1.2.
Ecrire un programme qui affiche les valeurs 0.1 0.2 0.3 .. jusqu'à 10.0.
résultat:
6,8,4,13,
#include <iostream>
using namespace std;
int main()
{
for(int i=2; i<=8; i=i+2)
cout<<"i="<<i<<endl; // pas d'accolades
for(int i=2; i<=8; i=i+2)
{ // accolade de debut de bloc
int j=i*i;
cout<<"i="<<i << "\t "<<"j="<<j <<endl;
} //accolade de fin
}
résultat:
i=2
i=4
i=6
i=8
i=2 j=4
i=4 j=16
i=6 j=36
i=8 j=64
Exercice 4.1.5.
“Cercle qui tourne”(*):
En faisant une boucle sur l'angle qui varie entre et par pas de radian, faire un programme où l'on voit en animation un cercle de rayon , qui parcourt le cercle de rayon dans le sens trigonométrique.
Aide: utiliser la librairie root, Section
3.4, et les formules de trigonométrie
,
. Pour utiliser les fonctions trigonométriques
cos et
sin il faut rajouter
#include <math.h> en haut du programme. Le nombre
est connu en C++ en écrivant
M_PI
Pour temporiser le programme, on pourra utiliser la Section
17.2.1 avec un délai de
entre chaque dessin.
4.2 Ecrire une condition
On a utilisé ci-dessus la condition i <= 30 qui signifie “ inférieur ou égal à ”. Voici la syntaxe pour écrire d'autres conditions:
4.2.1 Les opérateurs de comparaison:
Signification
|
symbole
|
supérieur à
|
>
|
inférieur à
|
<
|
supérieur ou égal à
|
>=
|
inférieur ou égal à
|
<=
|
égal à
|
==
|
différent de
|
!=
|
Attention à la confusion possible entre == et =:
a == 2 sert à tester l'égalité de l'objet a avec 2 (cela ne change pas la valeur de a). Par contre a = 2 met la valeur 2 dans l'objet a.
4.2.2 Les opérateurs logiques
Signification
|
symbole
|
et
|
&&
|
ou
|
||
|
non
|
!
|
Exercice 4.2.1.
Que fait le programme suivant? (deviner puis ensuite essayer pour vérifier)
#include <iostream>
using namespace std;
main ()
{
int a=1, b=2, c=3;
if( ((a <= b ) || (b >=c) ) && (a<c) )
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
4.2.3 Remarque
Lorsqu'une condition est évaluée comme i<=30, la valeur rendue est de la classe boolean (vrai ou faux).
4.3 La boucle “do {..} while (..);”
signifie fait le bloc d'instructions {..} tant que la condition (..) est vraie.
#include<iostream>
using namespace std;
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); //condition
}
résultat:
1 1
2 4
etc...
10 100
4.4 La boucle “while (..) {..} ;”
signifie tant que la condition (..) est vraie, fait le bloc d'instructions {..} .
#include <iostream>
using namespace std;
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
}
Résultat:
1 1
2 4
etc...
10 100
4.5 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;
}
}
Résultat
4
Exemple plus général:
On peut compléter avec “if.. else if.... else..” comme ceci:
#include<iostream>
using namespace std;
int main()
{
for(int i=1; i<=10; i=i+1)
{
if (i == 4)
cout<<"i="<<i<<endl;
else if(i==5)
cout<<"2*i= "<<2*i<<endl;
else
cout<<"."<<endl;
}
}
Résultat:
.
.
.
i=4
2*i= 10
.
.
.
.
.
Exercice 4.5.1.
“erreurs?”(*) On souhaite afficher 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;
}
4.6 break : pour sortir de la boucle. continue: pour sauter l'instruction.
#include <iostream>
using namespace std;
int main()
{
for(int i=2;i<=10;i=i+2)
{
if (i==4)
continue; // on passe au suivant.
if (i==8)
break; // on sort de la boucle
int j=i*i;
cout<<i << "\t "<<j <<endl;
}
}
Ce programme produira:
2 4
6 36
Exercice 4.6.1.
“nombre au hasard”(*)
Faire un programme qui au départ choisit un nombre au hasard entre 0 et 1000 (Utiliser la Section
3.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 maximum.
Chapitre 5 Les fonctions
Une fonction est un petit sous programme qui effectue 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.
5.1 Exemple de fonction qui renvoit un objet
Dans l'exemple suivant, la fonction carre calcule j=i*i. Cette fonction prend en entrée un objet int i et renvoit double j en sortie. Pour cela on utilise la syntaxe double carre(int i).
#include<iostream>
using namespace std;
//======declaration de la fonction carre ===================================
double carre(int i)
{
double j=i*i;
return j; //on renvoie le résultat au programme principal
}
//===declaration de la fonction principale =====
int main()
{
int x;
cout<<"entrer x "<<endl;
cin>>x;
double y=carre(x); // appel de la fonction carre
cout<<"Le carré de "<<x<<" est "<<y<<endl;
}
résultat:
entrer x
2
le carré de 2 est 4
5.2 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
}
résultat:
entrer x
2
le carré de 2 est 4
5.3 Paramètres des fonctions par référence
Quand le programme principal fournit un objet à une fonction, on peut vouloir que celle-ci modifie le contenu de l'objet. Dans l'exemple précédent la procédure ou la fonction carre ne modifie 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 modifié par la fonction. On dit que ce sont des paramètres passés par référence.
#include<iostream>
using namespace std;
//====== fonction permute ==================
void permute(int& i,int& j) // passage des parametres par reference
{
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;
}
résultat:
10 5
5 10
Exercice 5.3.1.
Executer ce programme, puis enlever les signes & (de référence) dans le passage des paramètres, et ré-essayer. Conclusion?
5.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 diffé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.
5.4.1 Exemple:
#include<iostream>
using namespace std;
//-------------------------------------------
void f(int i)
{
cout<<"fonction 1 appelée"<<endl;
cout<<" paramètre = "<<i<<endl;
}
//-------------------------------------------
void f(char *s,int i)
{
cout<<"fonction 2 appelée"<<endl;
cout<<" paramètre = "<< s <<endl;
cout<<" paramètre = "<< i <<endl;
}
//---------------------------------------------
int main()
{
f(10);
f("Chaîne ",4);
}
Renvoit
fonction 1 appelée
paramètre = 10
fonction 2 appelée
paramètre = Chaîne
paramètre = 4
Exercice 5.4.1.
“factorielle” (*)
Ecrire une fonction que l'on appelera factorielle qui en entrée prend un objet x de la classe long, et en sortie renvoit sa factorielle x! de la classe long. 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;
afin qu'il affiche comme résultat:
a= 5 a!=120
Chapitre 6
Les pointeurs
6.1 Déclaration et affectation d'un pointeur
La notion de pointeur est importante dans le langage C et C++. Elle est réputée comme étant difficile 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 effet de réserver un ”objet” en mémoire de l'ordinateur, permettant de stocker un nombre entier. Cet objet s'appelle 'i' son type (ou sa classe) est int.
Bien sûr, cet objet se trouve quelque part dans la mémoire de l'ordinateur. Il a un certain emplacement, caractérisé par son “adresse mémoire”, appellée son pointeur.
Il faut donc retenir que pointeur d'un objet signifie adresse d'un objet dans la mémoire de l'ordinateur.
On peut avoir accès à l'adresse de l'objet i en faisant &i:
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 sur entier (c'est à dire une adresse d'un objet contenant un entier).
- Le signe &i signifie l'adresse de l'objet i.
- (*p) signifie l'objet situé à l'adresse du pointeur p.
- Si fonction() est une fonction appartenant à une classe et si p est un pointeur d'un objet de cette classe, alors
l'instruction p->fonction(); est équivalente à (*p).fonction();
Schéma qui résume ce que l'on vient d'expliquer:
Conséquences:
pour afficher le contenu de l'objet i on a maintenant deux possibilités qui sont équivalentes:
cout<<i;
ou:
cout<<(*p);
Pour modifier le contenu de l'objet i on a maintenant deux possibilités qui sont équivalentes:
i=5;
ou:
(*p)=5;
6.1.1 Paramètres de la fonction main()
Jusqu'à présent, nous avons écrit la fonction principale du programme par la déclaration int main() {...}
En fait cette fonction peut prendre des paramètres et renvoyer un entier. Cela peut être utile pour passer des paramètres à un programme et obtenir des paramètres en retour. Voici un exemple que l'on commente ensuite:
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
cout<<"nombres de parametres argc ="<<argc<<endl;
cout<<" Ce sont:"<<endl;
for(int i=0;i<argc; i++)
{
cout<<"argv["<<i<<"] = "<<argv[i]<<endl;
}
return 0; // renvoit l'entier 0 (signifie habituellement 0K)
}
Exécution
./test 2 toto 5
Résultat:
nombres de parametres argc =4
Ce sont:
argv[0] = ./test
argv[1] = 2
argv[2] = toto
argv[3] = 5
Commentaires
On a lancé le programme par la commande ./test 2 toto 5 qui contient en effet 4 paramètres, le premier étant le nom du programme lui même. Cet exemple montre que au début du programme main(..) , la variable argc contient le nombre de paramètres et argv est un tableau de chaines de caractères pour chacun de ces paramètres.
6.2 Allocation dynamique de la mémoire
On peut réserver en mémoire de l'ordinateur une suite de n objets de type double par l'instruction:
p=new double[n];
Après cela, p pointe sur le premier objet réservé.
Cela s'appelle une allocation dynamique de la mémoire, car elle se fait au cours de l'éxécution du programme.
Il y a n objets, numérotés de 0 à n-1.
on peut écrire dans la case du premier objet 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].
A la fin de l'utilisation, n'oubliez pas de libérer l'emplacement mémoire par l'instruction:
delete [ ] p; // on libere les cases mémoire
Exemple:
#include<iostream>
using namespace std;
int main()
{
int n;
cout <<"entrez nbre d'objets n >=0 ?"<< 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
//--- remplissage des cases memoires
for(int i=0; i<n; i++)
p[i]=2*i;
//---- affichage
for(int i=0; i<n; i++)
cout<<" p["<<i<<"]="<<p[i]<<endl;
delete [ ] p; // on libere les cases mémoire
}
Résultat:
entrez nbre d'objets n >=0 ?3
p[0]=0
p[1]=2
p[2]=4
6.2.0.1 Remarques
- On peut réserver un nombre fixe 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 fixé à la compilation, et ne peut être variable. On peut aussi déclarer et initialiser en même temps:
double p[6] = {1, 3.14, 3., 0.};
- Si l'on veut ne réserver qu'une seule case mémoire au lieu d'un tableau, il suffit 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
- Un tableau de caractères est aussi appelé une chaîne de caractères. Pour ce cas il y a une initialisation spéciale:
char *chaine = “toto”;
qui déclare chaine comme étant un pointeur sur caractère, pointant sur les 4 caractères toto.
Exercice 6.2.2.
“pointeurs” (*) 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 fin, on affiche 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
6.3 Utilisation des pointeurs avec la librairie graphique ROOT
Dans l'exercice
4.1.5 vous avez dessiné une ellipse qui se déplace à l'aide d'une boucle “for”. Comment dessiner plusieurs cercles simultanéments? Nous vous proposons deux possibilités. La première utilise une liste (classe vector), la deuxième possibilité utilise des pointeurs.
6.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()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas c( "c","fenetre",400,400); // on precise la taille en pixels
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();
}
Ainsi l'ellipse numéro i+1 n'efface pas l'ellipse numéro i.
6.3.1.1 Remarques:
- Rappel: dans l'instruction “for”, l'instruction i++ est équivalente à i = i+1. De même i-- est équivalente à i = i-1
- Pour enlever l'ellipse numéro 1 de la liste, il faut utiliser la fonction erase() de la classe vector:
tab_e.erase(tab_e.begin()+1);
Rajouter cette ligne au bon endroit, dans le programme précédent et observer le résultat.
6.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()
{
TApplication theApp("App", nullptr, nullptr);
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();
}
6.3.2.1 Remarques:
Cette dernière méthode est simple, et voici ce qu'il se passe: pe est un pointeur sur un objet de la classe TEllipse autrement dit l'adresse de l'objet. L'instruction pe= new TEllipse() crée un objet ellipse et affecte pe à 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 fin 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éfini d'ellipses. Pour modifier 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).
Exercice 6.3.1.
“Tableau coloré” (*) Ecrire un programme qui dessine un tableau de 10*8 cercles colorés au hasard comme cela:
Chapitre 7 Création d'une classe
Programmer une classe consiste à définir 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 effectuer 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
3.3. Considérez donc cette Section comme étant à but pédagogique.
7.1
Exemple (étape1)
Un vecteur de est définit par ses trois composantes . Voici un programme de départ pour définir un vecteur en C++. Essayez ce code. Lisez ensuite les commentaires.
Code:
#include <iostream>
using namespace std;
#include <math.h>
// ===============déclaration de la classe ===============
class Vect
{
public:
double x,y,z;
};
//====== Programme principal =======================
int main()
{
Vect v; // declaration
v.x=1; v.y=2; v.z=3;
cout << "composante v.y = " << v.y << endl;
}
Résultat:
composante v.y = 2
7.1.1 Commentaires
- Dans la première ligne du programme principal on a déclaré un objet v de la classe Vect. 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 membres. A la deuxième ligne du programme principal, le code v.x=1; met la valeur 1 dans la variable membre x de l'objet v. De façon générale objet.variable est la façon de désigner une variable associée à un objet.
- L'annonce public: 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 fin de la déclaration de la classe.
Exercice 7.1.1.
En modifiant le programme précédent, créer une classe appelée Musicien qui contient les variables membres: string nom; string instrument; int age; string style; . Dans le programme principal on crée un objet de cette classe: Musicien robert_J; on initialise les variables et on affiche la variable instrument.
7.2
Exemple (étape2)
On complète le programme précédent. Essayez ce code. Lisez ensuite les commentaires.
Code:
#include <iostream>
using namespace std;
#include <math.h>
// ===============déclaration de la classe ===============
class Vect
{
public:
//---- variables membres
double x,y,z;
//-- le constructeur---
Vect()
{
x=0; y=0; z=0;
cout << "vecteur construit... " << endl;
}
//-- le destructeur---
~Vect()
{
cout << "vecteur détruit.... " << endl;
}
//--la fonction Norme---
double Norme()
{
double n=0;
n=x*x+y*y+z*z;
return sqrt(n);
}
};
//====== Programme principal =======================
int main()
{
Vect v; // declaration
v.x=1; v.y=2; v.z=3;
cout << "Norme = " << v.Norme() << endl;
}
Résultat:
vecteur construit...
Norme = 3.74166
vecteur détruit....
7.2.1 Commentaires
- Commençons par la première ligne du programme principal: on a déclaré un objet v de la classe Vect. Cela a pour effet d'appeler la fonction Vect() déclarée dans la classe, appelée 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 membres. Dans notre exemple la fonction constructeur a pour effet de mettre les variables x,y,z à zéro et d'afficher le texte “vecteur construit...”.
- Ainsi à la deuxième ligne du programme principal, le code v.x=1; met la valeur 1 dans la variable membre x de l'objet v. De façon générale 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 v.Norme() appèle la fonction Norme() qui est déclarée dans la classe. Cela a pour effet de calculer et afficher De façon générale objet.fonction() est la façon d'appeler une fonction membre associée à un objet.
- a la fin 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 effet d'afficher le texte "vecteur détruit.... ".
- Dans la déclaration d'une fonction membre comme Norme() les variables membre x,y,z s'accèdent directement (sans la syntaxe v.x) car ce sont sans ambiguité les variables membre de l'objet en cours pour qui la fonction a été appelée, ici v.
- La fonction membre constructeur Vect() est appelée chaque fois qu'un objet Vect 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). Cette fonction particulière ne renvoit rien, pourtant on n'écrit pas void 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 Vect) avec ~ devant . Cette fonction particulière ne renvoit rien, pourtant on n'écrit pas void ~Vect().
Exercice 7.2.1.
“Classes” (*)
- Ecrire le programme ci dessus dans un fichier vecteur.cc (dans un nouveau répertoire /classes/), éxécutez le, et vérifiez le comportement attendu.
- Rajouter une fonction membre de la classe Vect que l'on appelera Affiche(), et qui affiche à 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.
- Rajouter une fonction membre de la classe Vect que l'on appelera double Produit(Vect v2), et qui permet de calculer le produit scalaire entre deux vecteurs. Le résultat est un double. Dans le programme principal, l'appel doit se faire sous la forme :
Vect v,w;
double d=v.Produit(w);
Aide: dans la fonction double Produit(Vect v2), on écrira return x*v2.x+y*v2.y+z*v2.z;
- 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 affectations.
- Rajoutez un constructeur qui permet d'initialiser directement les variables membres x,y,z du vecteur. Il aura la syntaxe Vect(double xi,double yi,double zi). Dans le programme principal, on pourra alors déclarer un objet par Vect v(1,2,3);
Remarque: vous gardez le constructeur précédent Vect() qui ne prend pas d'argument. N'oubliez pas en effet qu'en C++ des fonctions différentes peuvent avoir le même nom et ne différent que par leurs arguments.
7.3 Utilisation de pointeurs sur objets
Nous avons expliqué les pointeurs dans la section
6. Dans l'exemple
7.2 ci-dessus, remplacer les quelques lignes du programme principal par
//====== Programme principal =======================
int main()
{
Vect *w = new Vect(); // declaration
w->x=0; w->y=3; w->z=4;
cout << "Norme de w = " << w->Norme() << endl;
}
Résultat:
vecteur construit...
Norme de w = 5
Commentaires
- La déclaration Vect *w signifie que w est un pointeur (c'est à dire une adresse) sur un objet de la classe Vect. le code w = new Vect(); a pour effet 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 x de l'objet pointé par w. De façon générale pointeur->variable est la façon de désigner une variable associée à un objet qui est pointé par le pointeur.
- A la troisième ligne du programme principal, le code w->Norme() appèle la fonction Norme() qui est déclarée dans la classe. De façon générale pointeur->fonction() est la façon d'appeler une fonction membre associée à un objet pointé par le pointeur pointeur.
Chapitre 8 Les fichiers
Jusqu'à présent, nous avons lu des données au clavier et nous les avons affiché à l'écran. On peut aussi écrire des données dans un fichier (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 fichiers. Ces fonctions sont regroupées dans le fichier fstream et font partie des classes ofstream pour écrire dans un fichier ou ifstream pour lire dans un fichier.
Traduction: Output (=sortie) File (=fichier) Stream (= flux de données), ou Input(=entrée),etc..
Reference:
8.1 Ecriture de données dans un fichier
Par exemple pour écrire un chiffre dans un nouveau fichier que l'on appellera hector.txt, il suffit de faire 3 étapes:
#include <iostream>
using namespace std;
#include <fstream> // utilisation des fichiers
int main()
{
ofstream f("hector.txt"); // ouvre le fichier hector.txt pour y ecrire. On lui associe l'objet: f
f<<3.1514<<endl; // permet d'ecrire dans le fichier.
f.close(); // fermeture du fichier f
}
8.1.0.1 Remarque:
- On a choisit de terminer le nom du fichier hector.txt par le suffixe .txt. Ce n'est pas une obligation, mais une convention: txt signifie texte, et précise que ce fichier peut se lire comme un texte (même si il y a des chiffres). De la même façon le fichier qui contient votre programme se termine par .cc pour signifier que c'est le texte d'un programme C++.
- Pour écrire dans le fichier, on a utilisé un objet intermédiaire de la classe ofstream (abréviation de output-file-stream).Cet objet a été initialisé avec le nom du fichier, et pour écrire dans le fichier on utilise la même syntaxe que pour écrire à l'écran (avec f à la place de cout).
- L'ouverture du fichier se fait par l'initialisation de l'objet f avec le nom du fichier 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);
- Lorsque l'on a effectué l'opération f.close(), f est un objet de la classe ofstream et close() est une fonction de cette classe. On parle de fonction membre. Remarquez la syntaxe: l'objet et la fonction membre sont reliés par un point.
- Attention, la commande ofstream f("hector.txt"); ouvre le fichier mais efface les données existantes si il y en avait. Pour rajouter des données à un fichier existant (ou non existant) il faut l'ouvrir avec l'instruction ofstream f("hector.txt",ios::app); (“app” signifie “append”, “ajouter”)
8.1.0.2 Exercice
Ecrire le programme précédent et vérifier l'existence du fichier. Ouvrir le fichier dans codeblocks ou avec un autre éditeur et vérifier son contenu.
8.2 Lecture de données depuis un fichier
Le programme suivant permet de lire le chiffre écrit précédement dans le fichier hector.txt.
#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
}
8.2.0.1 Remarques:
- Si le début du fichier n'avait pas contenu de chiffre (mais des lettres ou rien du tout) le programme n'aurait rien mit dans l'objet x.
- On peut de la même façon écrire ou lire plusieurs données à la suite dans un fichier: il est important de savoir que le programme est au départ positionné au début du fichier, 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.
- La lecture g>>x depuis le fichier est similaire à la syntaxe de lecture cin>>x depuis le clavier.
- A la place de cout<<x<<endl; on peut écrire les instructions:
if(g.good())
cout<<x<<endl;
En effet g.good() renvoit true si la lecture précédente s'est bien faite.
Exercice 8.2.1.
(*) Reprendre et modifier le programme de l'exercice
4.6.1 pour que les numéros proposés par l'utilisateur soient ecrit dans un fichier.
8.3 Suppléments sur les fichiers
Voici quelques exemples qui montrent comment lire des lignes, des caractères dans un fichier.
#include <iostream>
using namespace std;
#include <string>
#include <fstream>
int main()
{
//... ecrit des lignes de texte dans un fichier
ofstream f("test.txt");
f<<"ligne1: abc"<<endl;
f<<"ligne2: def"<<endl;
f.close();
//... ouvre le fichier en lecture
ifstream g("test.txt");
string l;
while(g.good())
{
getline(g, l);// -> l. copie la ligne dans l (sans le code retour a la ligne)
cout<<l<<endl; // affiche ligne l
}
g.clear(); // remet les indicateurs à zero pour continuer la recherche.
//....
g.seekg(0, g.beg); // se place au debut du fichier (begining + 0)
int p;
p=g.tellg(); // -> position p dans le fichier
cout<<"p="<<p<<endl;
char c;
for(int i=0; i<=5; i++)
{
g.get(c); // -> c. Lit caractere
cout<<c;
}
cout<<endl;
p=g.tellg();
cout<<"p="<<p<<endl;
}
résultat
ligne1: abc
ligne2: def
p=0
ligne1
p=6
Chapitre 9
Micro-projets 1
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), afin de vous montrer l'aspect ludique et créatif de la programmation, et aussi pour résumer diffé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
16.
Nous demandons de produire un compte rendu sous forme pdf et html, que l'on peut rédiger par exemple avec
lyx. Voir Section
14.6.
La Section suivante donne quelques recommandations générales et prélables pour effectuer un projet de programmation.
9.1 Que faire avant de programmer?
Avant de se retrouver devant un ordinateur pour écrire un programme, il faut avoir établi clairement ce qu'on désire lui faire faire. Voici l'ensemble des étapes à suivre, indispensables, à faire au préalable devant une feuille de papier.
- Définir clairement l'objectif du programme (surtout s'il y a plusieurs personnes à y collaborer).
- Définir l'ensemble des tâches qui doivent être accomplies dans un ordre logique pour atteindre cet objectif. Pour un projet scientifique il faut que toutes les formules mathématiques soient bien écrites.
- Tracer sur un papier un organigramme illustrant le fonctionnement de votre programme. Vérifiez que l'ordre d'exécution des diverses tâches va effectivement faire ce que vous attendez.
En fait, votre organigramme se traduira directement dans la partie principale de votre programme (fonction int main()). A chaque tâche correspondra l'appel d'une sous partie (une fonction).
- Déterminez la méthode (algorithme) permettant de remplir chaque tâche. Il est possible qu'une sous-tâche soit commune à plusieurs fonctions. Dans ce cas, faites-en une nouvelle fonction. Cependant, pour éviter une multiplication de ces fonctions, ne le faites que pour celles faisant plusieurs lignes de programme. L'art de la programmation consiste à trouver un compromis entre la simplicité, la longueur, et la rapidité d'éxécution de votre programme.
- Si le programme effectue des taches complexes et “imprévisibles”, imaginer des situations simples où l'on connait la solution et qui permettront de tester le programme.
9.1.1 Quelques notions de qualité en informatique
La qualité d'un programme ne se traduit pas simplement en termes d'efficacité mais également en termes de sécurité (fiabilité) et d'exportabilité. Autrement dit, vous devez écrire votre programme de telle sorte (1) que n'importe qui puisse le lire et comprendre ce qu'il fait et (2) qu'il puisse être aisément testé. Voici quelques indications:
9.2 Suite de Syracuse
Difficulté: facile.
Soit un nombre entier . Par récurrence, on définit à partir de par:
Observer que la valeur de départ donne la suite infinie périodique
La valeur de départ donne la suite
La fameuse
conjecture de Syracuse non démontrée à ce jour est que partant de toute valeur
la suite arrive forcément au bout d'un moment
(qui dépend de
) à la suite périodique
Exercice 9.2.1.
Ecrire un programme qui demande à l'utilisateur et affiche la suite jusqu'à ce que la valeur soit atteinte.
Solution 9.2.2.
Faire:
- Ecrire une fonction qui prend un entier en entrée et renvoit si est pair ou sinon.
- Dans le programme principal, on demande à l'utilisateur. Tant que , faire
- Affiche
Exercice 9.2.3.
Pour donné, on note la plus petite valeur de telle que . Tracer en fonction de .
Solution 9.2.4.
Voici l'algorithme. Faire une fonction qui prend un entier en entrée et renvoit :
- On pose .
- Tant que faire:
- on calcule .
Ensuite,
- Créer une fenetre graphique, avec des axes et ,
- on parcourt ,
- on calcule
- on dessine un point en
9.3 Pendule simple
Difficulté: 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
, longeur
, fixées, soumis à la force de pesanteur
avec
et à une force de frottement de coef
.
Equations physiques:
si est la position angulaire et la vitesse angulaire au temps on aoù sont des fonctions de . (vérifier ces formules en appliquant la loi de Newton sur la droite verte tangente au cercle.).
Position du pendule:
Méthode de résolution numérique
Avec la méthode de Euler, on considère un petit intervalle de temps ; au premier ordre en on a:En effet ces formules découlent de , et de même pour .
Voici comment on utilise ces formules. On choisit un pas de temps très petit. Par exemple On discrétise le temps avec un pas de temps : la date de départ est . ensuite il y a , , etc..
On suppose que sont les conditions initiales connues. Avec les formules de Euler on calcule , puis
On fait le dessin du pendule au fur et à mesure du calcule.
Structure du programme
Ecrire une fonction:
/* Formule du champ de vecteur sur le plan (a,b)
entree: a,b
sortie: Fa,Fb
*/
void F(double a, double b, double & Fa,double & Fb)
{
//... code ici
}
Dans la fonction main, partir de valeurs initiales, calculer à chaque pas de temps et dessiner le pendule.
9.4 La fractale du dragon
Difficulté: moyenne.
Si on prend une feuille de papier que l'on plie
fois et que l'on impose au pli de faire un angle droit (90°) on obtient la forme suivante. Si on plie
fois ou
fois ou ...
fois on obtient les formes suivantes assez surprenantes. La forme pour
s'appelle la
fractale du dragon.
Ecrire un programme qui demande une valeur et dessine la forme obtenue si l'on plie fois. Qu'observez vous de surprenant?
Exercice 9.4.1.
On peut coder la forme à fixé, comme une suite de valeurs , où (respect. ) signifie que le pli est à droite (respect. à gauche). Par exemple , , . Montrer que la suite se déduit facilement de la suite .
Solution 9.4.2.
Si la suite est connue alors où est la suite écrite à l'envers avec l'opposé des éléments.
Exercice 9.4.3.
En programation, on peut utiliser la classe vector<int> S; qui est une liste d'entiers, avec les instructions S.push_front(j); pour rajouter la valeur j au début, et S[k]; qui donne la valeur à la position k. Ecrire l'algorithme de cette suite.
Solution 9.4.4.
est donné. On part de la liste . On pose . Tant que faire:
- On copie . On pose .
- On rajoute au début de la liste .
- Pour , on rajoute au début de la liste .
- On copie . On fait .
Exercice 9.4.5.
Ensuite, pour la partie graphique, il suffit de lire cette liste et dessiner des segments à la suite. Ecrire l'algorithme.
Solution 9.4.6.
On code une direction par un chiffre: dir=0,1,2,3 pour respectivement: droite, haut, gauche, bas.
- On initialise et et .
- On calcule à partir de de la façon suivante:
- Si alors
- Si alors
- etc
- On change de direction: . (Remarque: on a écrit +4 car il y a un “bug” sur le modulo en langage C/C++.)
- On dessine une droite de à .
- On pose et .
- Si on retourne en (2).
9.5 Images subliminales
Dans “
le code de la conscience” de Stanilas Dehaene, p.66, il est expliqué qu'une image de durée <0.33 sec et entourée d'autres images ne sera pas percue conscienment. Effectuer ce type d'expériences avec des formes géométriques.
9.6 Equation de la chaleur
Difficulté: moyenne.
Notons
les variables d'espace et
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
appelée
équation de la chaleur, avec l'opérateur Laplacien:
On va modéliser cette évolution en dimension . On discrétise l'espace avec un pas très petit. On note donc , avec . Le champ de températures à l'instant est modélisé par une matrice de taille . On discrétise le temps avec un pas très petit et on note le champ de température à l'instant .
On remplaçant les dérivées par des différences finies montrer que l'équation de la chaleur ci-dessus devient:
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 . Pour les matrices, on utilisera la classe mat de armadillo.
9.7 Le jeu puissance 4
niveau facile (difficile si option de stratégie).
- Faire un programme qui permet de jouer à deux personnes au jeu de puissance 4, et affiche le résultat sur le terminal (avec cout). Stocker l'état du jeu dans une matrice d'entiers.
- En option, on peut jouer contre l'ordinateur. Développer une stratégie par recherche arborescente.
9.8 Le jeu de la vie
Difficulté: moyenne.
En utilisant une matrice d'entiers de taille 10*10 avec des conditions périodiques, faire évoluer et dessiner les coefficients de matrice selon les règles simples du
jeu de la vie.
9.9 Algorithme de Monte-Carlo pour le modèle d'Ising
Difficulté: moyenne à difficile.
On considère un réseau périodique dont les sites sont notés et dont les variables sont et modélisent des spins (up/down). Si deux sites sont voisins (chaque site a 4 voisins) on note . Une configuration des spins est un champ donné: . Son énergie ferromagnétique est:
9.9.1 Algorithme de Monte-Carlo
L'algorithme d'évolution suivant est appelé
algorithme de montecarlo
.
est un paramètre fixé qui est l'inverse de la température:
.
- A l'instant , on part d'une configuration choisie au hasard.
- On choisit un site au hasard, et on note la configuration identique à sauf au site où le spin est opposé: (spin opposé).
- Choix de la configuration à l'instant ,
- Si on choisit la configuration .
- Si on choisit la configuration avec la probabilité (et on choisit avec la probabilité complémentaire).
- On revient en (2) pour poursuivre l'évolution du champ de spins.
Exercice 9.9.3.
- Montrer que la condition s'écrit simplement en fonction du site et de ses voisins.
- Ecrire un programme qui fait évoluer et représente la configuration du champ de spins selon l'algorithme de Monte-Carlo. (Utiliser une matrice d'entiers avec armadillo et le dessin de TH2D expliqué en Section 16.3.1 avec l'option “col”).
- L'aimantation de la configuration est . Représenter l'aimantation au cours du temps. Quand l'équilibre statistique est atteind, calculer la moyenne et la variance . Représenter et en fonction de .
9.10 Zeros d'un polynome aléatoire
On considère un polynome de degré et à coefficients aléatoires et indépendants, selon une loi uniforme :
On sait que
a
zéros
dans le plan complexe. Dessiner les
zéros du polynome et observez. Quelle conjecture avez vous? Consulter cet
article de Terence Tao et al.. On peut aussi considérer des coefficients complexes aléatoires:
avec
aléatoires et indépendants.
Aide: les zéros du polynome sont les valeurs propres de la “
matrice compagnon” du polynome. Construire cette matrice et utiliser la fonction eigen() de
armadillo pour trouver les valeurs propres.
sls
Partie II
Compléments sur le langage C++
Chapitre 10 Autres environnements
10.1 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 nécessite un certain apprentissage, qu'il effectue automatique certaines taches à votre place et vous perdez un peu le contrôle de votre programmation.
- Lancer le programme codeblocks.
- Choisir "create a new project”
- 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 apparaît un fichier appelé main.cpp contenant le code C++ suivant:
#include <iostream>
using namespace std;
int int main()
{
cout << "Hello world!" << endl;
return 0;
}
- 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
- La commande du menu Plugins/SourceCodeFormatter vous permet de mettre votre programme en forme standard. Faites le souvent.
- 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 modifier 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.
Chapitre 11 Ecrire et lire des données en binaires sur un fichier (ou un flux)
11.1 Bits, octets, bytes, taille d'un objet
Or dans la mémoire de l'ordinateur une objet numérique (ex: int i=17 ) est stockée en binaire, i.e. en base 2, sous forme d'une suite de 0 et 1 appelés des bits. Une séquence de 8 bits est appellée un octet.
Le
byte est un ensemble de bits qui définit l'unité de stockage. En général un byte contient 8 bits, mais cela peut varier selon l'ordinateur. La variable système CHAR_BIT contient le nombre de bits contenus dans un byte. En général CHAR_BIT=8.
La commande sizeof(type) indique le nombre byte occupé par un type donné. Le programme:
#include<iostream>
using namespace std;
#include <limits.h>
int main()
{
cout<<"sizeof(int)="<<sizeof(int)<<endl;
cout<<"CHAR_BIT="<<CHAR_BIT<<endl;
}
affiche:
sizeof(int)=4
CHAR_BIT=8
Par exemple l'objet int i=17 est stocké en mémoire en base 2 par une suite de 4*8=32 bits soit 4 octets:
00010001 00000000 00000000 00000000
Classe
|
char
|
short int
|
int
|
long
|
float
|
double
|
Taille (en byte)
|
1
|
2
|
4
|
4
|
4
|
8
|
Exemple:
#include<iostream>
using namespace std;
#include <bitset>
int main()
{
int i=17;
cout<<"La variable int i="<<i<<" est de taille "<<sizeof(i)<<" bytes."<<endl;
unsigned char *p=(unsigned char*)&i; // Le pointeur p est l'adresse memoire de i
cout<<"Contenu des bytes, en ecriture base 10:"<<endl;
for(int i=0; i<sizeof(i);i++)
cout<<" "<<(int)p[i];
cout<<endl<<"Contenu des bytes, en ecriture base 2:"<<endl;
for(int i=0; i<sizeof(i);i++)
cout<<" "<<bitset<8>(p[i]);
cout<<endl;
}
Affiche:
La variable int i=17 est de taille 4 bytes.
Contenu des bytes, en ecriture base 10:
17 0 0 0
Contenu des bytes, en ecriture base 2:
00010001 00000000 00000000 00000000
11.2 Fichiers en format texte
Dans les leçons précédentes, nous avons vu comment écrire des données numériques dans un fichier. Ces données étaient écrites sous forme de chiffres (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é en base dix par un ou plusieurs chiffres dans le fichier.
Lorsque l'on écrit dans un fichier en format texte (par la commande fichier<<i<<endl; ) l'ordinateur convertit le nombre en base dix et écrit la suite des chiffres, 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 fichier à 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
11.3 Fichiers en format binaire
Il y a cependant la possibilité d'écrire les données dans un fichier 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 fichier.
11.3.0.1 Exemple
Le programme suivant montre comment écrire et lire en format binaire, et comment se positionner dans un fichier.
#include <stdlib>
#include <iostream>
using namespace std;
#include <fstream>
#include <iomanip>
int main()
{
double x=3.14;
int i=17;
int nx=sizeof(double); //nombre de byte occupés par un objet de classe double
int ni=sizeof(int); //nombre de byte occupés par un objet de classe int
cout<<"nx= "<<nx<<" ni= "<<ni<<endl;
//===============================
ofstream sortie( "donnees.dat ",ofstream::binary); //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 ", ifstream::binary); //-- 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;
int pos=entree.tellg(); // donne la position du curseur dans le fichier (en byte)
cout<<"la position du curseur est maintenant: "<<pos<<endl;
}
11.3.0.2 Remarques
Pour se positionner en lecture, il faut utiliser la fonction seekg. Pour se positionner en écriture, il faut utiliser la fonction seekp. Le premier argument de ces fonctions indique le déplacement relatif du pointeur (en byte). Le deuxième argument (facultatif) indique l'origine du déplacement. Ce peut-être:
- le début du fichier: ios::beg
- la position actuelle du curseur: ios::cur
- la fin du fichier ios::end
exemple: entree.seekg(nx,ios::beg); // on se positionne après nx octets en partant du début
Chapitre 12 Quelques trucs utiles en C++
12.1 Quelques commandes utiles en C++
12.1.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 affiche la liste des fichiers:
#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)
}
12.2 Preprocesseur de g++
- L'option de compilation -DTOTO est ensuite reconnue dans le code c++:
#ifdef TOTO
//... do something
#elif
//.. do something
#endif
12.3 Utiliser du code fortran en c++
12.3.1 Exemple 1:
Fichier: testC.cpp
#include <iostream>
using namespace std;
extern"C" {
void fortfunc_(int *ii, float *ff);
}
main()
{
int ii=5;
float ff=5.5;
fortfunc_(&ii, &ff);
return 0;
}
Fichier: testF.f
subroutine fortfunc(ii,ff)
integer ii
real*4 ff
write(6,100) ii, ff
100 format('ii=',i2,' ff=',f6.3)
return
end
Compile:
gfortran -c testF.f
g++ -c testC.cpp
g++ -o test testF.o testC.o -lgfortran
Run:
./test
Result:
ii= 5 ff= 5.500
12.3.2 Example 2
This is not yet available in c++ or c, that provides
for real variables in
bessel_first.html with boost library.
Remark: an other library is proposed
here.
Import the file mod_zbes.f90 from the link above and add:
Fichier: testC.cpp
// -*- mode:C++ ; compile-command: "gfortran -c mod_zbes.f90; gfortran -c testF.f; g++ -c testC.cc; g++ -o test mod_zbes.o testF.o testC.o -lgfortran" -*-
// execute command: ./test
#include <iostream>
using namespace std;
#include <boost/math/special_functions/bessel.hpp> // for cyl_bessel
typedef struct{double r; double i;} COMPLEX2;
//---------------------------------
extern"C"
{
//... use small capital for the function name
void bessel_(COMPLEX2 *znu, COMPLEX2 *zz, COMPLEX2 *zans, int *info);
}
//---------------------------------
int main(int argc, char **argv)
{
//... input
COMPLEX2 znu, zz;
znu.r = 1; // real part
znu.i = 0; // imag part
zz.r = 1;
zz.i = 0;
cout<<"order nu = ("<<znu.r<<","<<znu.i<<")"<<", argument z = ("<<zz.r<<","<<zz.i<<")"<<endl;
//... output
int info;
COMPLEX2 zans;
bessel_(&znu, &zz, &zans, &info);
cout<<"Result: J_nu(z) = ("<<zans.r<<","<<zans.i<<")"<<endl;
cout<<"info = "<<info<<", (0 is OK)"<<endl;
cout<<"Compare with boost library: J_nu.r(z.r) = "<<boost::math::cyl_bessel_j(znu.r, zz.r)<<endl;
return 0;
}
Fichier: testF.f
subroutine bessel(znu,zz,zans,info)
USE mod_zbes,ONLY: kp,bessel1,bessel2,hankel1,hankel2
COMPLEX (kp), INTENT (IN) :: znu, zz
COMPLEX (kp), INTENT (OUT) :: zans
INTEGER, INTENT (OUT) :: info
CALL bessel1(znu, zz, zans, info)
return
end
Compile:
gfortran -c mod_zbes.f90
gfortran -c testF.f
g++ -c testC.cc
g++ -o test mod_zbes.o testF.o testC.o -lgfortran
Run:
./test
Result:
order nu = (1,0), argument z = (1,0)
Result: J_nu(z) = (0.440051,5.55112e-17)
info = 0, (0 is OK)
Compare with boost library: J_nu.r(z.r) = 0.440051
Chapitre 13 Classes (suite)
Cette Section fait suite à la Section
7.
13.1 Surdéfinitions d'opérateurs
13.1.1 Exemple d'opérateur binaire
Pour effectuer 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éfinissant 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:
int main()
{
double d;
Vect v(1,2,3);
Vect w(2,3,4);
d=v*w;
cout <<”d=” << d << endl;
}
13.1.2 Commentaire
- La syntaxe de l'opération * est semblable à celle de la fonction membre Produit. Comme pour Produit, Il est important de remarquer Vect v2 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 *.
- 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 Vect v2).
- Les symboles possibles pour un opérateur binaire sont:
(),[ ], -> , *,/,%,+,-, << ,>>,<,<=,>,>=,==,!=,&,^, ||,&&,|,=,
+=,-=,*=,/=,%=,&=,^=,|=, <<=,>>=,et la virgule ,.
Exercice 13.1.1.
(*)
- Ecrivez le code pour effectuer la somme (termes à termes) et la différence de deux vecteurs par la syntaxe: w=u+v; w=u-v; et testez.
- Ecrivez le code pour effectuer le produit et la division externe d'un réel avec un vecteur v (produit de chaque élément) par la syntaxe w=v*a; ou w=v/a; et testez.
- On aimerait accéder aux variables membres v.x,v.y,v.z respectivement, par la syntaxe v[1],v[2],v[3]. Cela est possible en définissant 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 affectation comme v[1]=3;. Cela sera mieux expliqué au paragraphe “affectations”.
13.1.3 Exemple d'un opérateur unaire
Comme en mathématiques, on aimerait donner un sens à qui est l'opposé du vecteur . Pour cela il faut écrire:
Code à rajouter dans le code des fonctions membres:
Vect operator-()
{
return (*this)*(-1);
}
Utilisation dans le programme principal:
int main()
{
Vect v(1,2,3);
Vect w;
w=-v;
w.Affiche();
}
13.1.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 signifie 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 13.1.2.
- Simplifiez 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.
- Simplifiez le code de l'opérateur binaire w=v/a déjà écrit en utilisant l'opérateur binaire * .
13.2 Fonctions amies
13.2.1 Exemple
La syntaxe que permet les fonctions membres ci-dessus a une petite limitation. Dans la classe Vect, on aimerait par exemple définir le produit externe * d'un double avec un Vect v, et écrire: 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:
int main()
{
double d(2);
Vect v(1,2,3);
Vect w;
w=d*v;
}
13.2.2 Commentaire
- Le préfixe friend signifie 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 avec la classe. Remarquez l'ommission du préfixe de rattachement 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;
- Comme en mathématiques, on a définit a*v=v*a. Mais on pourrait écrire ce que l'on veut et ne pas respecter la commutativité.
- la déclaration double d(2) est équivalente à double d=2. (La signification est que le constructeur double(int) est appelé.)
13.2.3 Exemple de cout <<
On aimerait pouvoir afficher le contenu du vecteur v au moyen de la commande: cout v; Remarquons que l'objet à gauche de l'opérateur est ”cout” appartenant à la classe ostream (ici “o” signifie output donc affichage, et cette classe standard du C++ est obtenue par #include <iostream>). Si l'on veut définir 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:
int main()
{
Vect v(1,2,3);
cout v endl;
}
Exercice 13.2.1.
(*)
Ecrivez le code nécessaire afin de pouvoir demander le contenu d'un vecteur à l'utilisateur par la syntaxe cin v; L'utilisateur doit entrer les composantes à la suite des questions 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 est modifié à la sortie de la fonction.
13.3 Vecteurs de taille variable
On va améliorer la classe de vecteurs de précédente, en permettant d'avoir des vecteurs de avec 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 modification 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'affectation =.
Exercice 13.3.1.
Créer une nouvelle classe de vecteurs, qui contient les variables membres ci-dessus, avec les fonctions membres suivantes:
- Une fonction membre Libere() qui libère l'espace mémoire de taille N.
- Une fonction membre 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 , 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 Libere())
13.3.1 Constructeur par recopie
Dans un exercice précédent, nous avons écrit une fonction membre double Produit (Vect v2). Lors de l'appel de cette fonction par la syntaxe v.Produit(w) dans le programme 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 effectuée une recopie simple des variables membres (il s'agissait de x,y,z).
Dans le cas actuel, une recopie simple des variables membres ne suffit pas. A la création du nouvel objet Vect v2, il faut en effet 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)...
}
13.3.2 Opérateur d'affectation =
Dans la classe Vect de , si v,w sont deux vecteurs, et que l'on écrit l'opération d'affectation :
v=w;
alors les composantes de w sont copiées dans celles de v. Cette opération d'affectation 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'affectation =
Code à rajouter dans le code des fonctions membres:
Vect & operator=(const Vect & v2)
{
// ... code à écrire (exercice)...
}
Exercice 13.3.2.
- Ecrivez le code du constructeur par recopie et de l'opérateur d'affectation = ci dessus, et testez. (en affichant des messages).
- Reprenez les fonctions membres de la classe Vect de précédente, et écrivez les versions “Vect de “ correspondantes.
Concernant spécialement l'opérateur [ ], avec é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'afficher un message d'erreur (i.e. si i<0, ou i>N-1).
- Dans un deuxième temps, on aimerait que le premier indice soit quelconque (et pas forcément ). Par exemple, que les trois composantes d'un vecteur de soit v[2],v[3],v[4],i.e.
Précisement, on voudrait qu'un vecteur soit caractérisé par sa taille , et par son premier indice , afin d'accéder aux éléments du vecteur par la commande v[i] avec .
Il faut donc rajouter comme variable membre de la classe, et modifier les constructeurs et destructeurs, et fonctions membres si besoin est. A la fin, on aura une classe de vecteur assez sophistiquée, et prête à l'emploi.
- (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éfinir 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 14
Gestion de projets avec fichiers .h, Makefile, Git et Doxygen
14.1
Projet avec plusieurs fichiers, fichier .h
Si on developpe un projet il est parfois utile d'écrire le code c++ dans différents fichiers. 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 fichiers:
- un fichier main.cc qui contient la fonction main() par laquelle commence le programme.
- un fichier autres.cc qui contient une classe Vect et une fonction f utilisées dans la fonction main.
- un fichier main.h qui contient les déclarations de la classe Vect et de la fonction f. Le suffixe .h signifie header (comme “entêtes”).
14.1.0.1 Résultat du programme:
Norme de v=1
3*3=9
14.1.0.2 Code c++
//== fichier main.cc =============
#include <iostream>
using namespace std;
#include "main.h"
int main()
{
Vect v;
v.x=1;
cout<<"Norme de v="<<v.Norme()<<endl;
cout<<"3*3="<<f(3)<<endl;
}
//== fichier autres.cc =============
#include "main.h"
#include <math.h>
//--- contenu du constructeur
Vect::Vect()
{
x=0; y=0; z=0;
}
//-----contenu de la fonction Norme
double Vect::Norme()
{
return sqrt(x*x+y*y+z*z);
}
//--- code de la fonction f
double f(double x)
{
return x*x;
}
//== fichier main.h =============
#if !defined(__MAIN_H)
#define __MAIN_H
//------ d'une classe
class Vect
{
public:
double x,y,z;
Vect(); // constructeur
double Norme();
};
// ----- declaration d'une fonction
double f(double x);
#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 fichier main.h se trouve répété à cause d'inclusions multiples (par exemple le fichier A.cc inclut main.h et B.h, et le fichier 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é”.
14.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
14.2
Un programme Makefile pour gérer la compilation
Dans l'exemple de la section
14.1.0.3, si les fichiers sont importants et si on effectue une modification du code que dans le fichier
main.cc, on souhaiterait de pas refaire la commande de compilation “
g++ -c autres.cc -o autres.o”. Le programme
Makefile permet de gérer cela automatiquement (il regarde les dates de modification des fichiers).
Code de base qui doit être dans un fichier appelé Makefile
# --- fichier Makefile
main.o: main.cc main.h
g++ -c main.cc -o main.o
autres.o: autres.cc main.h
g++ -c autres.cc -o autres.o
final : main.o autres.o
g++ main.o autres.o -o main
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 fichier Makefile, les deux lignes
main.o: main.cc main.h
g++ -c main.cc -o main.o
signifient que si on souhaite “main.o”, cela dépend des fichiers main.cc et main.h, et si un de ces derniers a été modifié il faut éxécuter la commande g++ -c main.cc -o main.o
- la commande make final signifie que l'on souhaite “final”. Cela a pour effet d'enclencher les commandes de compilation nécessaires. Essayer par exemple de modifier seulement le fichier main.cc et refaites make final. Le résultat est:
g++ -c main.cc -o main.o
g++ main.o autres.o -o main
14.2.1 Un programme Makefile 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 suffit de la faire à un seul endroit. De plus grâce au symbols $@ et $* on évite des réécritures.
# --- fichier Makefile
# .... librairies
LIBR = -lm
# ... instructions de compilation
CC =g++ -std=c++11
OBJETS= main.o autres.o
main.o: main.cc main.h
$(CC) -c $*.cc -o $@
autres.o: autres.cc main.h
$(CC) -c $*.cc -o $@
final : main.o autres.o
$(CC) $(OBJETS) -o main $(LIBR)
lib:
ar r main
clean:
rm *.o
- 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'effacer tous les fichiers en langage machine .o par la commande make clean
- On a rajouté l'entrée lib pour créer une librairie.
14.2.2 Generation automatique du fichier makefile?
14.3 Gestion de versions d'un projet avec Git
Le logiciel
Git permet de gérer différentes versions d'un projet. Il se souvient de l'historique. On lui indique une liste de fichiers (“le dépot”) et régulièrement on lui demande de faire une sauvegarde (par l'instruction commit)
14.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 frederic.faure@univ-grenoble-alpes.fr
Cela modifie le fichier ~/.gitconfig
A faire au cours du projet en local:
- git status :montre l'état des fichiers par rapport au dépot local
- git add fichier.txt ou git add '*.cc' ou git add * pour ajouter un (des) fichier(s) au prochain commit (à faire chaque fois si nécessaire)
- git commit -m "first commit" fait une sauvegarde des fichiers du dépot avec le titre indiqué
- git log pour voir l'historique des opérations
- git rm fichier :enleve un fichier du dépot
- git mv file1 file2 : deplace ou change le fichier.
- git diff nom_fichier pour lire les differences
- Dans le fichier .gitignore il y a les extensions et repertoires à ignorer.
- git checkout -- nomfichier : revient à la version précédente (enleve les modifs récentes)
- git blame nom_fichier : montre l'auteur et dates de chaque ligne du fichier
- Stash:
- git stash save : sauve les modifs dans un coin (empile) et rétablie ancienne version.
- git stash drop : oublie la sauvegarde des modifications
- git stash apply : rappatrie la sauvegarde et laisse sur la pile
- git stash pop : rappatrie la sauvegarde et enlève de la pile
- git stash show : montre la pile de sauvegarde
14.4 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.
14.4.0.1 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
ou
git push -u projet master
Cela dépose les dernières modifications de votre projet sur le site web
- git pull
ou
git pull expanding_map master
Cela permet de récupérer les dernières modifications (posées par les collaborateurs) qui sont sur le site web.
- 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)
14.5 Commenter et documenter un projet avec Doxygen
Nous expliquons l'utilisation de base de
doxygen pour documenter automatiquement un projet en c++.
14.5.1 Création et consultation de la documentation
14.5.1.1 Création de la documentation html
- Se placer dans le répertoire du projet.
- Dans une console, lancer le programme doxywizard et configurer les paramètres. On génère la documentation par “Run”. Cela crée des répertoires: html et latex.
14.5.1.2 consultation de la documentation
- On lit le fichier hml/index.html
- Pour la documentation pdf, dans le répertoire latex, executer la commande make. Cela génère le fichier latex/refman.pdf
14.5.2 conventions pour écrire les commentaires
14.5.2.1 Page principale:
Ecrire:
/*! \mainpage Titre
\section Nom_section
texte...
*/
14.5.2.2 Le résultat suivant:
est obtenu avec le code suivant (syntaxe markdown):
Header 1 {#labelid}
========
## Header 2 ## {#labelid2}
[Link text](#labelid)
[Link text](@ref labelid)
- Item 1
More text for this item.
- Item 2
+ nested list item.
+ another nested item.
- Item 3
1. First item.
2. Second item.
lignes:
- - -
______
single asterisks*
_single underscores_
double asterisks**
__double underscores__
The link text](http://example.net/)
[The link text](http://example.net/ "Link title")
[The link text](/relative/path/to/index.html "Link title")
[The link text](somefile.html)
[The link text](@ref MyClass)
![Caption text](/path/to/img.jpg)
![Caption text](/path/to/img.jpg "Image title")
![Caption text][img def]
![img def]
[img def]: /path/to/img.jpg "Optional Title"
http://www.example.com>
<https://www.example.com>
<ftp://www.example.com>
<mailto:address@example.com>
<address@example.com>
First Header | Second Header
------------- | -------------
Content Cell | Content Cell
Content Cell | Content Cell
| Right | Center | Left |
| ----: | :----: | :---- |
| 10 | 10 | 10 |
| 1000 | 1000 | 1000 |
This is a paragraph introducing:
~~~~~~~~~~~~~~~~~~~~~
a one-line code block
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~{.c}
int func(int a,int b) { return a*b; }
~~~~~~~~~~~~~~~
```
also a fenced code block
```
Mathematical Inline Formula:
\f$\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}\f$.
or
\f[
|I_2|=\left| \int_{0}^T \psi(t)
\left\{
u(a,t)-
\int_{\gamma(t)}^a
\frac{d\theta}{k(\theta,t)}
\int_{a}^\theta c(\xi)u_t(\xi,t)\,d\xi
\right\} dt
\right|
\f]
or
\f{eqnarray*}{
g &=& \frac{Gm_2}{r^2} \\
&=& \frac{(6.673 \times 10^{-11}\,\mbox{m}^3\,\mbox{kg}^{-1}\,
\mbox{s}^{-2})(5.9736 \times 10^{24}\,\mbox{kg})}{(6371.01\,\mbox{km})^2} \\
&=& 9.82066032\,\mbox{m/s}^2
\f}
14.5.3 Fichier de commentaires avec la syntaxe markdown
On peut écrire le fichier README.md du projet avec la syntaxe “markdown”. Voici une référence sur
Markdown. avec
tutorial.
14.6
Compte rendu de projet en pdf et html avec Lyx
Lyx est un logiciel qui produit des documents scientifiques en différents formats (pdf, html, etc).
La documentation est bien faite. Voici quelques informations précises.
- On peut exporter en html: Exporter en xhtml avec Lyxhtml. Insertion de videos sous format gif animé.
- On peut insérer des schémas techniques avec des notations en Latex, grâce au logiciel xfig ou inkscape (dans ce logiciel, on a les formules latex par extensions/rendu/Latex).
- Pour des présentation on peut choisir le style Beamer
Chapitre 15 Interface interactive pour le C++
- Il y a le projet cling. Voir Cling.
- Il y a le projet SWAN qui permet d'effectuer du C++ scientifique de façon interactive sur le “cloud”.
Partie III Compléments sur les librairies du langage C++
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 16
Librairie Root: graphisme et autre.. (compléments)
Nous avons déjà vu une introduction à cette librairie en Section
3.4. Cette Section apporte quelques compléments.
16.1 Graphisme de base 2Dim
16.1.1 Objets graphiques
Voici quelques exemples de
graphisme 2D. Remarquez que au début du programme on ajoute une ligne spécifique par exemple
#include <TLine.h> si on utilise la classe TLine.
#include <TApplication.h>
#include <TCanvas.h>
#include <TLine.h>
#include <TEllipse.h>
#include <TMarker.h>
#include <TBox.h>
#include <TText.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//.. 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 r
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();
}
16.1.2 Systèmes de coordonnées
Exemple:
#include <TApplication.h>
#include <TCanvas.h>
#include <TLine.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//.. La fenetre
TCanvas c("titre","titre",10,10,400,400); //position x,y sur l'ecran et taille X,Y en pixels
c.Range(0,0,10,10); // xmin,ymin,xmax,ymax, systeme de coordonnees
//.. une ligne bleue
TLine l;
l.SetLineColor(kBlue); // bleu
l.DrawLineNDC(0,0,1,1); // coordonnées universelles (0,1,0,1)
//.. une ligne rouge
TLine l2;
l2.SetLineColor(kRed); // rouge
l2.DrawLine(5,0,5,10); // coordonnées definies par Range()
c.Update(); // Montre le dessin
theApp.Run(); // garde la fenetre ouverte et permet a l'utilisateur d'interagir.
}
16.1.3 Dessiner dans plusieurs fenêtres
#include <TApplication.h>
#include <TCanvas.h>
#include <TMarker.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas c1("c1","fenetre1",400,400); // creation de la fenetre c1
TCanvas c2( "c2","fenetre2",400,400); // creation de la fenetre c2
TMarker *m1=new TMarker(0.3,0.4,8); // un point
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
theApp.Run();
}
16.1.4 Un cadre avec des axes gradués: DrawFrame
#include <TApplication.h>
#include <TCanvas.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas *c=new TCanvas("titre","titre",400,400);
double x1(0),y1(0),x2(2),y2(1); // position du cadre
c->DrawFrame(x1,y1,x2,y2);
theApp.Run();
}
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 modifier le cadre, par exemple:
#include <TApplication.h>
#include <TCanvas.h>
#include <TEllipse.h>
#include <TH1F.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas *c=new TCanvas("titre","titre",400,400);
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();
}
16.1.5 Un seul axe gradué: TGaxis
#include <TApplication.h>
#include <TCanvas.h>
#include <TGaxis.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas *c=new TCanvas("titre","titre",400,400);
double x1(0.1),y1(0.5),x2(0.9),y2(0.5); // 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();
theApp.Run();
}
16.1.6 Couleurs
#include <TApplication.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TColor.h>
#include <TH3.h>
//====================
main ()
{
TApplication theApp("App", nullptr, nullptr);
//---- on defini une palette de 5 couleurs
const int n = 5; //nbre de couleurs
float rgb[n * 3] =
{0.9f, 0.60f, 0.f,
0.f, 1.f, 1.f,
1.f, 0.f, 0.f,
0.f, 1.f, 0.f,
1.f, 1.f, 0.f};
int palette[n] = {0};
for (Int_t i = 0; i < n; ++i)
palette[i] = TColor::GetColor(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
gStyle->SetPalette(n, palette);
//------------------------
gStyle->SetCanvasPreferGL(kTRUE); // permet d'utiliser openGL
TCanvas *c = new TCanvas("glc","TH3 Drawing", 400,400);
int N=50;
double x1=-3,x2=3,y1=-3,y2=3,z1=-3,z2=3;
TH3D *h = new TH3D("h", "h", N, x1, x2, N, y1, y2, N, z1, z2);
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
{
double x= (x2-x1)*i*1./N+x1;
double y= (y2-y1)*j*1./N+y1;
double z= (z2-z1)*k*1./N+z1;
double f= exp(-x*x-y*y-z*z); // formule f(x,y,z)
h->SetBinContent(i+1,j+1,k+1, f);
}
h->SetFillColor(kBlue);
h->SetContour(n);
h->Draw("gliso");// glbox1 glbox glcol gliso
theApp.Run();
}
16.2 Graphisme 1D
Voici quelques exemples d'utilisation des
Histogrammes. Pour utiliser ces exemples, il faut remplacer les parties (C) de l'exemple de la Section
3.4.2, par le code ci-dessous. Attention: la ligne
#include <..> mentionnée chaque fois est à mettre en haut de votre programme.
La page de
THistPainter montre les nombreuses possibilités de représentation avec les histogrammes. Nous présentons quelques une d'entre elles.
16.2.1 Courbe a partir de l'expression d'une formule : TF1
16.2.1.1 Exemple à partir d'une formule littérale:
#include <TApplication.h>
#include <TF1.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
TF1 *f1 = new TF1("f1","sin(x)/x",0,10);
f1->Draw();
//-------------------
theApp.Run();
}
16.2.1.2 Même résultat à partir d'une fonction C++
#include <TApplication.h>
#include <TF1.h>
//---------------------
double F(double *px, double *p)
{
double x= px[0];
return sin(x)/x;
}
//---------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
TF1 *f1 = new TF1("f1",F,0,10);
f1->Draw();
//-------------------
theApp.Run();
}
16.2.1.3 Courbe avec des labels aux axes
#include <TApplication.h>
#include <TF1.h>
#include <TH1.h>
//--defines a function-------------------
double F(double *px, double *p)
{
double x= px[0];
if(x>1)
return 0;
else
return (1. - pow(x, 3/2.));
}
//---------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
TF1 *f1 = new TF1("1-(T/T_{B})^{3/2}",F,0,2);
f1->Draw();
//... draw axis lables
TH1 * h = f1->GetHistogram();
h->SetXTitle("T/T_{B}");
h->SetYTitle("N_{0} / N");
//-------------------
theApp.Run();
}
16.2.2 Histograme 1Dim ou Courbe à partir de tableau de valeurs : TH1D
#include <TApplication.h>
#include <TH1.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
int nx(10); // nombre de points
double xmin(0.0),xmax(1.0); // graduation de l'axe x
TH1D *h1=new TH1D("nom","titre",nx,xmin,xmax);
h1->SetXTitle("x");
h1->SetYTitle("y");
for(int i=1;i<=nx;i++) // remplissage
{
double z=i*i;
h1->SetBinContent(i,z);
}
h1->SetStats(0);// pas de boite de resultats stat
h1->SetMarkerStyle(8);//8: gros point
h1->Draw("L P"); // option L: ligne ou P: marker
//-------------------
theApp.Run();
}
16.2.2.1 Représentation en diagramme “Camembert” (Pie Chart)
Même exemple ci-dessus avec l'option h1->Draw("PIE 3d"); et h1->Draw("PIE rsc");
16.2.3 Représentation d'un tableau de valeurs sous forme de “camembert” ou “Pie”
#include <TApplication.h>
#include <TPie.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
int n= 5; // nbre de valeurs
double valeurs[5] = {.2, 1.1, .6, .9, 2.3};
int couleurs[5] = {2,3,4,5,6};
TPie *p = new TPie("p", "titre", n, valeurs, couleurs);
p->Draw();
//-------------------
theApp.Run();
}
16.2.4 Courbe paramétrée avec axes à partir de tableaux de valeurs : TGraph
Attention: si les valeurs son équidistribuées il est préférable d'utiliser la classe TH1 ci-dessous. La classe TGraph est préférables si les valeurs et ne sont pas équidistribuées.
Exemple:
#include <TApplication.h>
#include <TGraph.h>
#include <TPad.h>
#include <TAxis.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//---- creation de tableaux
int n = 100;
double x[100], y[100];
for (int i=0;i<n;i++) // remplissage
{
double r = i;
x[i] = r*cos(0.3* i);
y[i] = r*sin(0.3 * i);
}
//--- dessin
TGraph *gr = new TGraph(n,x,y);
gr->SetLineColor(kBlue);
gr->SetMarkerColor(kRed);
gr->SetMarkerStyle(6); // 6:petit cercle 8: gros
gr->GetXaxis()->SetTitle("X");
gr->GetYaxis()->SetTitle("Y");
gr->Draw("A C P"); // AC: trace un segment entre les points
// gPad->BuildLegend(); // rajoute la legende
//-------------------
theApp.Run();
}
16.2.5 Courbe paramétrée représentée en coordonnées polaire à partir de points
#include <TApplication.h>
#include <TCanvas.h>
#include <TGraphPolar.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas * c = new TCanvas("CPol","TGraphPolar Example",500,500);
int N=50;
double theta[N];
double radius[N];
for (int i=0; i<N; i++)
{
theta[i] = i*(2*M_PI/N);
radius[i] = sin(theta[i])* sin(theta[i]) +1 ;
}
TGraphPolar * grP1 = new TGraphPolar(N, theta, radius);
grP1->SetFillColor(kRed);
grP1->Draw("f");
c->Update();
theApp.Run();
}
Autre exemple:
#include <TApplication.h>
#include <TCanvas.h>
#include <TGraphPolar.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas * c = new TCanvas("CPol","TGraphPolar Example",500,500);
double theta[8];
double radius[8];
double etheta[8];
double eradius[8];
for (int i=0; i<8; i++) {
theta[i] = (i+1)*(M_PI/4.);
radius[i] = (i+1)*0.05;
etheta[i] = M_PI/8.;
eradius[i] = 0.05;
}
TGraphPolar * grP1 = new TGraphPolar(8, theta, radius, etheta, eradius);
grP1->SetTitle("TGraphPolar Example");
grP1->SetMarkerStyle(20);
grP1->SetMarkerSize(2.);
grP1->SetMarkerColor(kBlue);
grP1->SetLineColor(kRed);
grP1->SetLineWidth(3);
grP1->Draw("PE");
c->Update();
theApp.Run();
}
16.2.6 Crée histogramme ou fonction 1D par tirage aléatoire et ajustement par une formule : TF1
Code:
#include <TRandom.h>
#include <TApplication.h>
#include <TCanvas.h>
#include <TF1.h>
#include <TH1.h>
#include <iostream>
using namespace std;
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas *c=new TCanvas("titre","titre",400,400);
//------- cree histo avec tirage aleatoire avec loi gaussienne ------------
int nbin=100; double xmin=-3,xmax=3;
TH1D *h=new TH1D("TW","TW",nbin,xmin,xmax);
gRandom->SetSeed(); // initialise sur l'horloge
for(int i=1;i<=1e5;i++) //tirages
{
double x= gRandom->Gaus(0,1); // c'est P(x)=exp(-0.5*(x/sigma)^2)
h->Fill(x);
}
h->Draw();
//---- calcule integrale et normalise l'histogramme -----------
double I= h->Integral();
h->Scale(1/I);
h->Draw();
//--- Fit l'histogramme avec une Gaussienne --------------
TF1 *f1 = new TF1("f1","gaus"); // ou pol0, pol1,...polN pour polynome degre N, ou expo
h->Fit("f1");
cout<<"Ecart type ="<<f1->GetParameter(2)<<endl;
//--- montre fenetres de commandes ---
h->DrawPanel();
h->FitPanel();
//------
c->Update();
//------
theApp.Run();
}
16.2.7 Superposition d'histogrammes: THStack
#include <TApplication.h>
#include <TH1.h>
#include <THStack.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//---- cree un histogramme --------
int nx(10); // nombre de points
double xmin(0.0),xmax(1.0); // graduation de l'axe x
TH1D *h1=new TH1D("nom","titre",nx,xmin,xmax);
h1->SetXTitle("x");
h1->SetYTitle("y");
for(int i=1;i<=nx;i++) // remplissage
{
double z=i*i;
h1->SetBinContent(i,z);
}
h1->SetFillColor(kGreen);
//-------------- une copie ----------
TH1D *h2 = (TH1D*)h1->Clone();
h2->SetFillColor(kRed);
//-------------------------
THStack *hs = new THStack("hs","");
hs->Add(h1);
hs->Add(h2);
hs->Draw(); // dessin les uns sur les autres. nostack: superposes , nostackb, lego1
//-------------------
theApp.Run();
}
16.3 Graphisme 2D
16.3.1
Surface 2Dim: à partir d'un tableau de valeurs : TH2D
#include <TApplication.h>
#include <TH2D.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
int nx(10),ny(10);
double xmin(0),xmax(1),ymin(0),ymax(2);
TH2D *h= new TH2D("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
//-------------------
theApp.Run();
}
16.3.2 Surface 2D a partir de l'expression d'une formule : TF2
Voici le résultat du programme suivant avec différentes options, en particulier, représentation cylindrique, polaire, spherique et en utilisant openGL.
#include <TApplication.h>
#include <TF2.h>
#include <TStyle.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
gStyle->SetCanvasPreferGL(kTRUE); //si option gl.. cidessous
TF2 *f2 = new TF2("f2","-sin(x)*sin(y)/(x*y)",0,5,0,5);
f2->Draw("surf4"); // glsurf1, glsurf2, glsurf3, glsurf3, glsurf1cyl, glsurf1pol, glsurf1sph
//-------------------
theApp.Run();
}
16.3.3 Surface 2D à partir d'un ensemble arbitraire de points : TGraph2D
La classe
TGraph2D permet de dessiner une surface
à partir de la donnée d'un ensemble de
points
.
#include <TApplication.h>
#include <TGraph2D.h>
#include <TCanvas.h>
#include <TRandom.h>
#include <TStyle.h>
using namespace std;
main ()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas *c = new TCanvas("c","Graph2D example",0,0,600,400);
double x, y, z, P = 6.;
int N = 200;
TGraph2D *g = new TGraph2D();
TRandom *r = new TRandom();
for(int i=0; i<N; i++)
{
x = 2*P*(r->Rndm(N))-P; // choix de (x,y) au hasard dans le carré [-P,P]
y = 2*P*(r->Rndm(N))-P;
z = (sin(x)/x)*(sin(y)/y)+0.2; // formule de la surface lisse
g->SetPoint(i,x,y,z); // ajoute point (x,y,z) a l'ensemble
}
gStyle->SetPalette(1); // couleurs. autres choix: 0, 1, 2
g->Draw("tri1"); // autres choix: tri, triw, tri1, tri2, p,po, pcol,line
theApp.Run();
}
16.3.4 Champ de vecteur en dimension 2
/// Ce code dessine un champ de vecteur spécifié à deux dimensions.
#include <TApplication.h>
#include <TCanvas.h>
#include <TArrow.h>
#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()
{
TApplication theApp("App", nullptr, nullptr);
//------------
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
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();
}
16.4 Graphisme 3D
16.4.1
Des points dans : TPolyMarker3D
Remarque: ensuite avec le menu on peut régler leur opacité etc
#include <TApplication.h>
#include <TPolyMarker3D.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
//------------
int N=1000;//nbre de points
TPolyMarker3D *p = new TPolyMarker3D(N);
for( int i = 0; i < N; i++ )
{
double theta=i/double(N)*2*M_PI;
double x=cos(theta), y=sin(theta), z=cos(10*theta);
p->SetPoint( i, x, y, z );
}
p->SetMarkerSize( 1 );
p->SetMarkerColor( kBlue);
p->SetMarkerStyle( 8 );
p->Draw();
//-------------------
theApp.Run();
}
16.4.2 Surface déterminée par : TF3
Ici la surface dans est déterminée par l'équation avec donnée par son expression.
On utilise la classe
TF3.
the options "FB" and "BB" suppress the "Front Box" and "Back Box" around the plot.
Attention: avec option 'gl', il semble qu'il y a un bug (corrigé dans les nouvelles version de ROot), la fonction trace et non pas .
given by:
#include <TApplication.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TFormula.h>
#include <TF3.h>
//-----------------------------
main ()
{
TApplication theApp("App", nullptr, nullptr);
gStyle->SetCanvasPreferGL(true);
auto c = new TCanvas("c", "TF3: Surface", 0, 0, 600, 600);
TF3 *tf3 = new TF3("Surface, option gl=0","x*y*z+0.2", -1,1,-1,1,-1,1);
tf3->Draw("glfbbb");
//------------------------
theApp.Run();
}
Or
given by:
#include <TApplication.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TFormula.h>
#include <TF3.h>
//-----------------------------
main ()
{
TApplication theApp("App", nullptr, nullptr);
auto c = new TCanvas("c", "TF3: Surface", 0, 0, 600, 600);
TF3 *tf3 = new TF3("Surface option gl=0","x*y*z", -1,1,-1,1,-1,1);
tf3->Draw("FBBB");
//------------------------
theApp.Run();
}
Another example:
#include <TApplication.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TFormula.h>
#include <TF3.h>
//-----------------------------
main ()
{
TApplication theApp("App", nullptr, nullptr);
gStyle->SetCanvasPreferGL(true);
auto c = new TCanvas("c", "TF3: Surface", 0, 0, 600, 600);
TFormula f1 = TFormula("f1", "x*x + y*y + z*z + 2*y - 1");
TFormula f2 = TFormula("f2", "x*x + y*y + z*z - 2*y - 1");
TF3 *tf3 = new TF3("Klein Bottle","f1*(f2*f2-8*z*z) + 16*x*z*f2",-3.5, 3.5, -3.5, 3.5, -2.5, 2.5);
tf3->SetFillColor(kRed);
tf3->Draw("glFB");
//------------------------
theApp.Run();
}
16.4.3 Fonction : TH3
Dessin avec openGL et en option: dessin de la surface de niveau de (“gliso”) ou boules montrant la valeur de (“glbox”) ou densité transparente “glcol”.
Remarques:
- avec l'option surface de niveau “gliso” la touche c permet de voir des coupes et les déplacer avec la souris.
#include <TApplication.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TH3.h>
main ()
{
TApplication theApp("App", nullptr, nullptr);
gStyle->SetCanvasPreferGL(kTRUE); // mettre avant declaration de la fenetre TCanvas
TCanvas *c = new TCanvas("glc","TH3 Drawing", 400,400);
int N=30;
double x1=-3,x2=3,y1=-3,y2=3,z1=-3,z2=3;
TH3D *h = new TH3D("h", "h", N, x1, x2, N, y1, y2, N, z1, z2);
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
{
double x= (x2-x1)*i*1./N+x1;
double y= (y2-y1)*j*1./N+y1;
double z= (z2-z1)*k*1./N+z1;
double f= exp(-x*x-y*y-z*z); // formule f(x,y,z)
h->SetBinContent(i+1,j+1,k+1, f);
}
h->SetFillColor(kBlue);
double levels[2] = {2, 2.5}; // valeurs des niveaux
h->SetContour(2,levels); // on choisit deux niveaux pour le dessin iso
h->Draw("gliso");// glbox1 glbox glcol gliso
theApp.Run();
}
16.4.3.1
Dessin d'une surface de niveau souhaitée
Sans l'option “gl”, mais seulement “iso”, la surface de niveau dessinée est pour la valeur . Par conséquent si on souhaite montrer la ligne avec arbitraire, il faut par exemple modifier le contenu du point en écrivant avec de sorte que
Voici un exemple qui dessine la surface c'est à dire la sphère de rayon 2:
#include <TApplication.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TH3.h>
main ()
{
TApplication theApp("App", nullptr, nullptr);
//-------------------
double C0=2; // niveau souhaite
TCanvas *c = new TCanvas("glc","TH3 Drawing", 400,400);
int N=30;
double x1=-3,x2=3,y1=-3,y2=3,z1=-3,z2=3;
TH3D *h = new TH3D("h", "h", N, x1, x2, N, y1, y2, N, z1, z2);
double sum=0;
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
{
double x= (x2-x1)*i*1./N+x1;
double y= (y2-y1)*j*1./N+y1;
double z= (z2-z1)*k*1./N+z1;
double f= sqrt(x*x+y*y+z*z); // fonction
h->SetBinContent(i+1,j+1,k+1, f);
sum+=f;
}
double delta = -sum + (N*N*N) *C0;
h->SetBinContent(1,1,1, h->GetBinContent(1,1,1)+delta);
h->SetFillColor(kBlue);
h->Draw("iso");
theApp.Run();
}
16.4.3.2 Dessin d'une courbe sur une surface
On peut combiner le dessin d'une iso-surface de la Section
16.4.3.1 avec le dessin d'une courbe de la Section
16.4.1 (mais sans l'option GL malheuresement).
Voici un exemple: un grand cercle équateur sur la sphère de rayon 2. Malheuresement, la partie arrière de la courbe n'est pas cachée.
#include <TApplication.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TH3.h>
#include <TPolyMarker3D.h>
main ()
{
TApplication theApp("App", nullptr, nullptr);
//-------------------
double C0=2; // niveau souhaite
TCanvas *c = new TCanvas("glc","TH3 Drawing", 400,400);
int N=30;
double x1=-3,x2=3,y1=-3,y2=3,z1=-3,z2=3;
TH3D *h = new TH3D("h", "h", N, x1, x2, N, y1, y2, N, z1, z2);
double sum=0;
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
{
double x= (x2-x1)*i*1./N+x1;
double y= (y2-y1)*j*1./N+y1;
double z= (z2-z1)*k*1./N+z1;
double f= sqrt(x*x+y*y+z*z); // fonction
h->SetBinContent(i+1,j+1,k+1, f);
sum+=f;
}
double delta = -sum + (N*N*N) *C0;
h->SetBinContent(1,1,1, h->GetBinContent(1,1,1)+delta);
h->SetFillColor(kBlue);
h->Draw("iso");
//------------
int N2=1000;//nbre de points
TPolyMarker3D *p = new TPolyMarker3D(N2);
for( int i = 0; i < N2; i++ )
{
double theta=i/double(N2)*2*M_PI;
double x=2*cos(theta), y=2*sin(theta), z=0;
p->SetPoint( i, x, y, z );
}
p->SetMarkerSize( 1 );
p->SetMarkerColor( kRed);
p->SetMarkerStyle( 8 );
p->Draw();
//-------------------
theApp.Run();
}
16.5 Bibliothèque TGeom pour objets géométriques 3D
Compile with the option -lGeom -lRGL
Result of the next program:
#include <TApplication.h>
#include <TCanvas.h>
#include <TGeoManager.h>
#include <TEveManager.h>
#include <TGLViewer.h>
//-----------------------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
double R=1, R2=0.2; // radius
double L = 0.5; //length
TGeoManager * geom = new TGeoManager("sphere", "spheres");
//--- define the global domain
TGeoMaterial *mat = new TGeoMaterial("Al");
TGeoMedium *med = new TGeoMedium("MED",1,mat);
double X= 10*R, Y=10*R, Z=10*R;
TGeoVolume *top = geom->MakeBox("TOP",med, X,Y,Z); // This is the global domain. (can be MakeTube also)
geom->SetTopVolume(top);
TGeoMaterial *mat2 = new TGeoMaterial("ball");
TGeoMedium *ball = new TGeoMedium("ball", 1, mat2);
//------------ object 1: spheres
TGeoVolume *o1 = geom->MakeSphere("SPHERE", ball, 0., R); // name, medium, rmin, rmax, thetamin=0, thetamax=180, phimin=0, phimax=360
o1->SetLineColor(kRed);
for(int i=0; i<4; i++)
{
//... center
double x = 0;
double y = 0;
double z = i * 3 * R;
top->AddNode(o1, i, new TGeoTranslation(x, y, z));
}
//------------ object 2: cylinders
TGeoVolume *o2 = geom->MakeTube("tube", ball, 0., R2, L); // name, medium, rmin, rmax, dz = half lenght
o2->SetLineColor(kBlue);
for(int i=0; i<4; i++)
{
double x =0;
double y = 0;
double z = 1.5+ i * 3 * R;
top->AddNode(o2, i, new TGeoTranslation(x, y, z));
}
//------------------
geom->CloseGeometry();
top->Draw("ogl"); // "ogl" - OpenGL viewer, "x3d"
auto gv = (TGLViewer*) gPad->GetViewer3D() ;
gv->SetClearColor(kBlack); // fond noir
gv->UpdateScene(); // Update
//------------------------
theApp.Run();
}
16.6
Utilisation de la souris
Dans une fenetre gaphique, chaque objet capture la souris si elle passe à proximité. Pour l'observer, vous pouvez activer l'option “EventStatus” dans le menu de la fenetre.
Voici un code complet qui definit une classe que l'on appelle Fenetre (héritée de la classe TCanvas). La nouveauté est la fonction HandleInput() 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 HandleInput(EEventType 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 faire appel à d'autres fonctions).
#include <iostream>
using namespace std;
#include <TCanvas.h>
#include <TMarker.h>
#include <TApplication.h>
#include <TEllipse.h>
//====Ma Classe Fenetre================================
class Fenetre: public TCanvas
{
public:
// constructeur ......
Fenetre(Text_t* name, Text_t* title, Int_t ww, Int_t wh) : TCanvas(name,title,ww,wh)
{ }
//—- Cette fonction est appelle si la souris s'agitte. ——
void HandleInput(EEventType event, Int_t px, Int_t py)
{
//– affiche l'etat de la souris ——–
cout<<" position: px="<<px<<" py="<<py<<" code du bouton="<<event<<endl;
//– convertit la position en pixel px,py en coordonnees (x,y) ——
double x = gPad->AbsPixeltoX(px);
double y = gPad->AbsPixeltoY(py);
// si bouton gauche appuye dessine un point —
if(event==1)
{
TMarker *m=new TMarker(x,y,8);
m->Draw();
Update();
}
//—————— Event clavier ——————–
if (event==kKeyPress) // touche appuyée
{
cout<<"touche px="<<px<<endl;
switch (px)
{
//——— change de mode (tempéré <-> juste) ——–
case 'm':
cout<<"Touche m appuyée"<<endl;
break;
}; // switch px
} // if event
}
};
///--------------------------------------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
Fenetre *c = new Fenetre("c", "fenetre1", 400,400); // crée une fenetre
double xmin(0),ymin(0),xmax(5),ymax(5);
c->Range(xmin,ymin,xmax,ymax); // coordonnees de la fenetre (optionnel, par defaut: 0-1)
theApp.Run();
}
16.6.1 Pour qu'un objet graphique capture les evenements souris
Dans l'exemple suivant une ellipse change d'état selon les evenement souris (clique gauche). Pour cela il faut créer une classe dérivée qui possède la fonction ExecuteEvent(int event, int px, int py).
#include <iostream>
using namespace std;
#include <TCanvas.h>
#include <TApplication.h>
#include <TEllipse.h>
//==============
class Cercle: public TEllipse
{
public:
// constructeur ......
Cercle(double x, double y, double r): TEllipse(x,y,r)
{ }
int col = kRed;
//—- Cette fonction est appelle si la souris s'agitte. ——
void ExecuteEvent(int event, int px, int py)
{
//--- change l'epaisseur du cercle
SetLineWidth(3);
Draw();
gPad->Update();
SetLineWidth(1);
Draw();
//--- si clique gauche
if(event == 1)
{
if(col == kRed)
col = kBlue;
else
col = kRed;
SetFillColor(col);
Draw();
}
}
};
///--------------------------------------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas *c = new TCanvas("c", "fenetre1", 400,400); // crée une fenetre
double xmin(0),ymin(0),xmax(4),ymax(4);
c->Range(xmin, ymin, xmax, ymax); // coordonnees de la fenetre (optionnel, par defaut: 0-1)
Cercle *C1= new Cercle(3,2,0.5);
C1->SetFillColor(kRed);
C1->Draw();
Cercle *C2= new Cercle(1,2,0.5);
C2->SetFillColor(kRed);
C2->Draw();
theApp.Run();
}
16.6.2 Pour gérer la souris lorsque le programme calcule
Si votre programme est lancé dans un calcul interminable, et que vous vouliez cependant modifier 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.
16.7 Comment créer un fichier gif animé
Voici un exemple complet qui permet de créer un fichier “film.gif” animé à partir d'images créées par root.
Travail préalable:
- Fichiers à télécharger et sauver dans son répertoire: bib_fred.h et bib_fred.cc.
- Il faut avoir installé le logiciel gifsicle,
#include <TApplication.h>
#include <TCanvas.h>
#include <TEllipse.h>
#include "bib_fred.cc"
///------------------------------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
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
}
theApp.Run();
}
Ensuite pour visualiser le film vous pouvez utiliser la commande firefox film.gif.
16.8 Boutons de commandes (Widgets et GUI)
Le mot “widget” est une contraction de “windows” et “gadget”. GUI signifie “Graphical User Interface”.
- Documentation
- Exemple très complet qui montre presque toutes les possibilités et comment le faire: guitest.cc à copier/coller. (attention, il y a une erreur: remplacer (char **) par (const char **)).
- Voir aussi les nombreux exemples dans /usr/share/doc/root/tutorials/gui que l'on execute avec l'interpréteur et la commande .x nom.C
16.8.1 Exemple simple avec quelques widgets
Voici un
exemple plus simple, à copier/coller, donnant l'interface:
/*! ===========================
* Exemple simple d'une fenetre de commande.
* Il y a deux classes: la classe Com qui est la fenetre de commande
* et qui permet de commander une autre classe A
* Chaque classe a un lien vers l'autre afin de pouvoir communiquer.
*/
#include <TCanvas.h>
#include <TGFrame.h>
#include <TGButton.h>
#include <TGComboBox.h>
#include <TGTextEntry.h>
#include <TGLabel.h>
#include <TGProgressBar.h>
#include <TPad.h>
#include <TFrame.h>
#include <TH1F.h>
#include<TSystem.h>
#include <TGNumberEntry.h>
#include <TGSlider.h>
#include <TGDoubleSlider.h>
#include <TGTripleSlider.h>
#include <TGMenu.h>
#include <TApplication.h>
#include <TRootEmbeddedCanvas.h>
#include <thread>
#include <chrono>
#include <iostream>
using namespace std;
class A; // classe definie après
/*!==========fenetre de commandes ========================
* Description de la classe Com
*/
class Com: public TGMainFrame // (derivee d une fenetre GUI)
{
public :
TGMenuBar *fMB; // barre de menu
TGPopupMenu *fM; // menu
TGTextButton *fB; // bouton
TGTextEntry *fT; // zone texte
TGLabel *fL; // texte fixe ecrit sur la fenetre
TGComboBox *fC; // liste deroulante
TGCheckButton *fCB;
TGHProgressBar *fH; // barre de progres
TGNumberEntry *fNE;
TGHSlider *fS;
TGDoubleVSlider *fS2;
TGTripleHSlider *fS3;
TRootEmbeddedCanvas *fEC; // fenetre
//-------------------
A *a; // lien vers un objet a d'une classe A
//---------------------
Com(A *a_i); // constructeur
~Com(); // destructeur
Bool_t ProcessMessage(Long_t msg, Long_t parm1, Long_t); // fonction appelee si action sur les commandes
//=========================================
// met a jour les valeurs de la fenetre
void Met_a_jour(); // fonction qui met à jour les valeurs de la fenetre à partir des parametres de l'objet a
};
/*!===============
* Une classe pour effectuer un calcul ...
*/
class A
{
public:
double x;
//-------------
void Calcul()
{
cout<<"calcul:";
for(x=1; x<=50; x++)
{
this_thread::sleep_for(chrono::milliseconds {100}); // attend 0.5 ms// attente,
com->Met_a_jour();
cout<<x<<","<<flush;
}
cout<<endl;
}
//------------
Com *com; // pointeur vers l'objet fenetre de control
void Lance_commandes()
{
TApplication theApp("App", NULL,NULL);
com= new Com(this); // cree fenetre de control et l'associe a l'objet present
theApp.Run(); //----donne la main a l'utilisateur ---
}
};
/*!=====================
Constructeur
*/
Com::Com(A *a_i) : TGMainFrame(gClient->GetRoot(),400,350) // taille de la fenetre
{
a=a_i; // initialise le lien vers l'objet à controller (sera utile pour les commandes)
a->com=this;
//----------------
SetWindowName("Fenetre de commandes"); // nom de la fenetre
//--------- Menu ----------
fMB = new TGMenuBar(this, 35, 50, kHorizontalFrame);
AddFrame(fMB, new TGLayoutHints(kLHintsTop | kLHintsExpandX, 2, 2, 2, 5));
fM=fMB->AddPopup("&Menu");
fM->AddEntry("&Option 1",3);
fM->AddSeparator();
fM->AddEntry("O&ption 2",4);
fM->Associate(this);
//-----boutons ------------------------
fB= new TGTextButton(this,"S&tart",2); // cree bouton, lettre t, et code message 2
fB->Move(100,10); // position x,y
fB->SetToolTipText("Demarre un calcul ..."); // texte d'aide
fB->Associate(this);
fB->Resize(80, fB->GetDefaultHeight()); // taille (x,y)
//..Label = texte fixe.................
fL= new TGLabel(this, new TGString("Label = Texte fixe"));
fL->Move(10,40); // position x,y
//...Zone de texte ................
string text="toto";
fT = new TGTextEntry(this,text.c_str(),1); //code 1
fT->Move(10,70); // position x,y
fT->Resize(40,fT->GetDefaultHeight()); // taille (x,y)
char buf[100]="texte";
fT->Associate(this);
//------- Liste Combo ----
fC = new TGComboBox(this, 88);
fC->AddEntry("entree 1",1);
fC->AddEntry("entree 2",2);
fC->Resize(150, 20);// taille (x,y)
fC->Select(2); // selection a priori
AddFrame(fC, new TGLayoutHints(kLHintsTop | kLHintsLeft,5,5,5,5)); // mise en association
fC->Move(10,100); // position
//----- Progress Bars--------------
fH = new TGHProgressBar(this,TGProgressBar::kFancy,300);
fH->ShowPosition();
fH->SetBarColor("green");
fH->Move(10,130); // position
fH->SetRange(0.0,50.); // min, max
fH->SetPosition(10.); // position
fH->ShowPosition(kTRUE,kFALSE,"valeur: %.0f");
//----------- Sliders -----------
fS = new TGHSlider(this,150,kSlider1|kScaleDownRight,2); // simple, Id=2
fS->SetRange(0,50);
fS->SetPosition(39);
fS->Move(10,200);
fS2 = new TGDoubleVSlider(this,100,kDoubleScaleNo,3); //double
fS2->SetRange(-10,10);
fS2->Move(200,200);
fS3 = new TGTripleHSlider(this,100,kDoubleScaleBoth,4, //triple
kHorizontalFrame);
fS3->SetConstrained(kTRUE);
fS3->SetRange(0,10);
fS3->SetPosition(2,3);
fS3 ->SetPointerPosition(2.5);
fS3->Move(250,200);
//-------- entree numerique ------
double val=3.1415;
fNE = new TGNumberEntry(this, val, 5, 400,(TGNumberFormat::EStyle) 2); // 5: longueur du nombre, 400: Id
fNE->Associate(this);
fNE->Move(10,250); // position
//----- Fenetre Canvas --------------
fEC = new TRootEmbeddedCanvas("ec1", this, 200, 100);
AddFrame(fEC, new TGLayoutHints(kLHintsTop | kLHintsLeft | kLHintsExpandX |
kLHintsExpandY, 5, 5, 5, 5)); // les options disent comme la fenetre se comporte si on maximise ou change la taille
fEC->Move(200,10);
TCanvas *c = fEC->GetCanvas();
c->DrawFrame(0,0,1,1);
c->Update();
// c->Modified();
//....Check Button
fCB= new TGCheckButton(this,":bouton check",420);
fCB->SetToolTipText("texte d'aide pour bouton check");
fCB->Associate(this);
fCB->Resize(120, fCB->GetDefaultHeight());
fCB->Move(10,280);
//------------
MapSubwindows(); // dessin des objets (bouttons ,...)
MapWindow(); // dessin de la fenetre
}
/*!====================================
* Destructeur
*/
Com::~Com()
{
delete fB;
delete fT;
delete fH;
delete fC;
}
/*!====================================
* Fonction appelee lorsque l'utilisateur agit sur la fenetre de commandes
* -> Ici on gère les actions
*/
Bool_t Com::ProcessMessage(Long_t msg, Long_t p1, Long_t p2)
{
int M = GET_MSG(msg), S=GET_SUBMSG(msg);
cout<<" M = "<<M<<" S = "<<S<<" p1="<<p1<<" p2="<<p2<<endl;
switch (M)
{
case 1:
switch (S)
{
case 3:
switch(p1)
{
case 2:
cout<<"bouton 2"<<endl;
a->Calcul();
break;
}
break;
case 4:
if(p1==420)
{
cout<<"bouton check .. "<<endl;
if(fCB->IsOn())
cout<<" on."<<endl;
else
cout<<" off."<<endl;
}
break;
case 7:
cout<<"combo choisi: "<<fC->GetSelected()<<endl;
break;
}
case 4:
switch (S)
{
case 1:
cout<<"texte change"<<endl;
switch (p1)
{
case 1:
cout<<"Le nouveau texte est: "<<((fT->GetBuffer())->GetString())<<endl;
break;
case 400:
cout<<"Le nouveau chiffre est: "<<((fNE->GetNumber()))<<endl;
break;
}
break;
}
break;
default:
break;
}
Met_a_jour();
return kTRUE;
}
/*! =========================================
* Fonction appelée par une fonction externe
* pour mettre a jour les valeurs de la fenetre,
*/
void Com::Met_a_jour()
{
fH->SetPosition(a->x); // progress Bar
// gClient->NeedRedraw(fH);
gSystem->ProcessEvents(); // handle GUI events
}
/*!===Main================================================
*/
int main(int argc, char **argv)
{
A *a=new A;
a->Lance_commandes();
return 0;
}
- fNumber->GetNumberEntry()->SetToolTipText("Whatever text you want to see here...") permet de rajouter un texte d'aide à un widget.
16.8.2 Exemple simple avec un menu, des zones, des tabs et une fenetre secondaire
/*! ===========================
* Exemple simple d'une fenetre de commande.
* avec Menu et Tab et sous fenetre libre
*/
#include <TCanvas.h>
#include <TGFrame.h>
#include <TGButton.h>
#include <TGMenu.h>
#include <TApplication.h>
#include <TGTab.h>
#include <iostream>
using namespace std;
//---- identifieurs (int) pour les messages de commandes
enum Command_id
{
M_1, M_2, M_3, M_4, B_1, B_2
};
//====================================
// Fenetre secondaire
class Fen2 : public TGTransientFrame
{
public:
//---- constructeur
Fen2(const TGWindow *p, const TGWindow *main, UInt_t w, UInt_t h, UInt_t options = kVerticalFrame): TGTransientFrame(p, main, w, h, options)
{
SetCleanup(kDeepCleanup);
//--------- zone avec bouton
auto fFrame1 = new TGHorizontalFrame(this, 60, 20, kFixedWidth);
auto fOkButton = new TGTextButton(fFrame1, "&Ok", B_1);
fOkButton->Associate(this);
fFrame1->AddFrame(fOkButton);
AddFrame(fFrame1);
//---------
MapSubwindows();
Resize(); // resize to default size
CenterOnParent(); // position relative to the parent's window
SetWindowName("2eme fenetre");
MapWindow();
//fClient->WaitFor(this); // otherwise canvas contextmenu does not work
}
//---- destructeur
virtual ~Fen2()
{
// Delete test dialog widgets.
}
//------
virtual void CloseWindow()
{
cout<<"fenetre Fen2 fermee��?"<<endl;
// Add protection against double-clicks
// fOkButton->SetState(kButtonDisabled);
DeleteWindow();
}
//------
virtual Bool_t ProcessMessage(Long_t msg, Long_t p1, Long_t p2)
{
int M = GET_MSG(msg), S=GET_SUBMSG(msg);
cout<<" Fen2: M = "<<M<<" S = "<<S<<" p1="<<p1<<" p2="<<p2<<endl;
}
};
/*!==========fenetre principales ========================
*/
class Com: public TGMainFrame
{
public :
int X=300, Y=150; // taille en pixels
//===== Constructeur
Com() : TGMainFrame(gClient->GetRoot(), X,Y)
{
SetCleanup(kDeepCleanup); //??
//-- regles de positionnement
TGLayoutHints *fLH_TX = new TGLayoutHints(kLHintsTop | kLHintsExpandX , 2, 2, 2, 5);
TGLayoutHints *fLH_C = new TGLayoutHints(kLHintsCenterX | kLHintsExpandX | kLHintsExpandY,5,5,5,5);
TGLayoutHints *fLH_L = new TGLayoutHints(kLHintsLeft,5,5,5,5);
TGLayoutHints *fLH_R = new TGLayoutHints(kLHintsRight,5,5,5,5);
//--------- Menu ----------
TGMenuBar *fMB = new TGMenuBar(this, X, Y);//, kHorizontalFrame);
AddFrame(fMB, fLH_TX);
TGPopupMenu *fM=fMB->AddPopup("&Menu1");
fM->AddEntry("&Fenetre",M_1);
fM->AddSeparator();
fM->AddEntry("O&ption 2",M_2);
fM->Associate(this);
TGPopupMenu *fM2=fMB->AddPopup("&Menu2");
fM2->AddEntry("&Option 1",M_3);
fM2->AddSeparator();
fM2->AddEntry("O&ption 2",M_4);
fM2->Associate(this);
//--------- zone superieure
auto * fFrame1 = new TGHorizontalFrame(this, X, Y);//, kFixedWidth);
AddFrame(fFrame1, fLH_L);
//.. bouton 1
TGButton *fOkButton = new TGTextButton(fFrame1, "&Ok ", B_1);
fOkButton->Associate(this);
fFrame1->AddFrame(fOkButton, fLH_L);
//.. bouton 2
TGButton *fOkButton2 = new TGTextButton(fFrame1, "&Ok2", B_1);
fOkButton2->Associate(this);
fOkButton2->Move(50,50);
fFrame1->AddFrame(fOkButton2, fLH_L);
//----- Tab ------------------
TGTab *fTab = new TGTab(this, X,Y);
AddFrame(fTab);
//.. Tab1 -----------
TGCompositeFrame *tf1 = fTab->AddTab("Tab 1");
//.. bouton 1
TGTextButton * fB1 = new TGTextButton(tf1, "&Bouton 1 LLLLLL", B_2);
fB1->Associate(this);
tf1->AddFrame(fB1);
//.. bouton 2
TGTextButton * fB2 = new TGTextButton(tf1, "&Bouton 2", B_2);
fB2->Associate(this);
tf1->AddFrame(fB2);
//.. Tab2 ..................
TGCompositeFrame *tf2 = fTab->AddTab("Tab 2");
//-- fenetre generale-----------
MapSubwindows();
Resize();
MapWindow();
SetWindowName("Fenetre de commandes"); // nom de la fenetre
}
//===========================
~Com() // destructeur
{
}
//=====================================
Bool_t ProcessMessage(Long_t msg, Long_t p1, Long_t p2)
{
int M = GET_MSG(msg), S=GET_SUBMSG(msg);
cout<<"Fen: M = "<<M<<" S = "<<S<<" p1="<<p1<<" p2="<<p2<<endl;
if(M==1 && S==1 && p1 ==M_1 && p2== 0)
new Fen2(fClient->GetRoot(), this, 400, 200); // nouvelle fenetre
return kTRUE;
}
}; // fin de la classe
/*!===Main================================================
*/
int main(int argc, char **argv)
{
TApplication theApp("App", nullptr, nullptr);
Com *com= new Com(); // cree fenetre de control et l'associe a l'objet present
theApp.Run(); //----donne la main a l'utilisateur ---
return 0;
}
16.8.3 Exemple simple avec des tabs, des sous-tabs et des graphiques (canvas)
#include <TGFrame.h>
#include <TGTab.h>
#include <TRootEmbeddedCanvas.h>
#include <TCanvas.h>
#include <TH1F.h>
#include <TApplication.h>
//===========================
void fsarazin()
{
const char *moni_histoname[] = {"moni_1", "moni_2","moni_3","moni_4","moni_5",0};
const char *offline_histoname[] = {"off_1","off_2","off_3","off_4","off_5",0};
TGLayoutHints *hint = new TGLayoutHints(kLHintsExpandX|kLHintsExpandY,2,2,2,2);
TGLayoutHints *hint_plots = hint;
// main frame
TGMainFrame *mainFrame = new TGMainFrame(gClient->GetRoot(),500, 300);
TGTab *tab = new TGTab(mainFrame, 1, 1);
TGCompositeFrame *moniFrame = tab->AddTab("Monitoring data");
TGTab *monitab = new TGTab(moniFrame,1,1);
moniFrame->AddFrame(monitab,hint_plots);
TGCompositeFrame *moni_hTab[5];
TRootEmbeddedCanvas *moni_Canvas[5];
for (int i=0;i<5;i++) {
moni_hTab[i] = monitab->AddTab(moni_histoname[i]);
moni_Canvas[i] = new TRootEmbeddedCanvas(moni_histoname[i], moni_hTab[i], 500, 300);
moni_hTab[i]->AddFrame(moni_Canvas[i], hint);
}
TGCompositeFrame *offlineFrame = tab->AddTab("Offline data");
TGTab *offlinetab = new TGTab(offlineFrame,1,1);
offlineFrame->AddFrame(offlinetab,hint_plots);
TGCompositeFrame *offline_hTab[5];
TRootEmbeddedCanvas *offline_Canvas[5];
for (int i=0;i<5;i++) {
offline_hTab[i] = offlinetab->AddTab(offline_histoname[i]);
offline_Canvas[i] = new TRootEmbeddedCanvas(offline_histoname[i], offline_hTab[i], 500, 300);
offline_hTab[i]->AddFrame(offline_Canvas[i], hint);
}
tab->Resize(tab->GetDefaultSize());
mainFrame->AddFrame(tab, hint);
TH1F *m_h[5];
for (int i=0;i<5;i++) {
m_h[i] = new TH1F(moni_histoname[i], moni_histoname[i], 100, -5, 5);
m_h[i]->FillRandom("gaus",15000);
m_h[i]->SetFillColor(i+2);
m_h[i]->SetFillStyle(3004);
moni_Canvas[i]->GetCanvas()->cd();
m_h[i]->Draw();
moni_Canvas[i]->GetCanvas()->Update();
}
TH1F *o_h[5];
for (int i=0;i<5;i++) {
o_h[i] = new TH1F(offline_histoname[i], offline_histoname[i], 100, 0, 50);
o_h[i]->FillRandom("landau",15000);
o_h[i]->SetFillColor(i+2);
o_h[i]->SetFillStyle(3004);
offline_Canvas[i]->GetCanvas()->cd();
o_h[i]->Draw();
offline_Canvas[i]->GetCanvas()->Update();
}
mainFrame->MapSubwindows();
mainFrame->Resize(mainFrame->GetDefaultSize());
mainFrame->MapWindow();
mainFrame->Resize(900,600);
}
//===========================
int main()
{
TApplication theApp("App", nullptr, nullptr);
fsarazin();
theApp.Run(); //----donne la main a l'utilisateur ---
}
16.8.4 Construire une interface de widgets à la souris avec GuiBuilder
Root propose un logiciel pour construire le code à la souris:
GuiBuilder. Cela permet au moins de voir la syntaxe du code pour réaliser une construction.
Voici comment l'utiliser.
- Dans un terminal de commandes lancer la commande root. Dans la ligne de commande qui apparait lancer la commande new TGuiBuilder. Cela lance le logiciel de creation d'interface.
16.8.5 Signal/Slots communication
Voir cette
documentation, en particulier l'exemple final que l'on peut compiler.
Attention:
classes should have a dictionary.
Fichier test.cc:
#include <TQObject.h>
#include <RQ_OBJECT.h>
class A {
RQ_OBJECT("A")
private:
Int_t fValue;
public:
A() : fValue(0) { }
~A() { }
void SetValue(Int_t value); // *SIGNAL*
void PrintValue() const { printf("value = %d\n", fValue); }
};
void A::SetValue(Int_t value) { // Set new value
// Emit signal "SetValue(Int_t)" with a single parameter
if (value != fValue) {
fValue = value;
Emit("SetValue(Int_t)", fValue);
}
}
// Main program
#ifdef STANDALONE
int main(int argc, char **argv) {
A* a = new A();
A* b = new A();
a->Connect("SetValue(Int_t)", "A", b, "SetValue(Int_t)");
printf("n******* Test of SetValue(Int_t) signal *******n");
b->SetValue(10);
printf("nt***** b before ******n");
b->PrintValue();
a->SetValue(20);
printf("t***** b after a->SetValue(20) ******n");
b->PrintValue();
return 0;
}
#endif
Lancer root, et écrire
.L test.cc++
résultat:
Info in <TUnixSystem::ACLiC>: creating shared library ./test_cc.so
Compilation
g++ -o test test.cc `root-config --cflags --libs` ./test_cc.so -DSTANDALONE
Execution
./test
Resultat
Error in <TQObject::CheckConnectArgs>: for signal/slot consistency
checking need to specify class name as argument to RQ_OBJECT macro
n******* Test of SetValue(Int_t) signal *******nnt***** b before ******nvalue = 10
t***** b after a->SetValue(20) ******nvalue = 10
16.9 Autres trucs
16.9.1 Nombre aléatoire avec Root
#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)
16.9.2 Trucs utiles avec les fenetres
- gROOT->SetStyle("Plain"); // style standard
- Rendre les dessins éditables (modifiables):
- gPad->SetEditable(0); // rend les dessins non modifiables
- gPad->SetEditable(1); // rend les dessins modifiables
- TCanvas *c=h->GetCanvas(); //Pour obtenir la fenetre TCanvas à partir d'un histogramme TH1 *h
- gStyle->SetPadGridX(kTRUE); // grilles en X,Y
gStyle->SetPadGridY(kTRUE);
Obtenir un pointeur sur TCanvas à partir du nom de la fenetre:
TCanvas *canvas = 0;
canvas = (TCanvas *)gROOT->GetListOfCanvases()->FindObject("canvas2");
Position d'une fenetre:
canvas->SetWindowPosition(Int_t x, Int_t y); // move the container window in a different position into the screen
canvas->SetWindowSize(UInt_t ww, UInt_t wh); //resize the container window (not the contained canvas)
canvas->RaiseWindow(); // to move the canvas window in front of all windows on the desktop.
16.9.3 Quelques Options générales de Root
avec
#include <TROOT.h>
- Mode Batch (sans sortie écran + rapide et permet de le lancer sur un serveur distant):
- gROOT->SetBatch(kTRUE); // met en mode Batch
- gROOT->IsBatch(); // permet de savoir si on est en mode batch.
Chapitre 17 Mesure du temps
On présente ici la classe
chrono.
17.1 Mesure de la date et du temps écoulé
17.1.0.1 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
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;
}
Affiche:
0 1 2 3 ...99996 99997 99998 99999
durée: 491264417ns = 491ms = 0.491264 s.
17.1.0.2 Affichage 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
17.2
Attendre
17.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
17.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;
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;
}
On obtient:
J'attends 3 secondes
La durée a été dt = 3.00016 s.
Chapitre 18 Calcul numérique avec une précision arbitraire
Utiliser la librairie C
arblib.
Chapitre 19 Intégrer des équations différentielles ordinaires (EDO) avec la librairie ODEINT
Introduction:
Une équation différentielle ordinaire (E.D.O.) est une équation de la forme:
où
est un vecteur qui dépend du temps
, et
est une fonction connue. On connait
et
l'on cherche
pour d'autres valeurs de
. Le
théorème de Cauchy Lipchitz garantit l'existence et l'unicité de
mais on a rarement une expression explicite de la solution. L'ordinateur est utile pour trouver des valeurs approchées de
.
Exemple
Pour intégrer avec la methode “runge_kutta_dopri5 with standard error bounds 10-6 for the steps”, le champ de vecteur dans du système de Lorenz défini par:avec les paramètres: , , , on écrit:
#include <iostream>
#include <boost/array.hpp>
#include <boost/numeric/odeint.hpp>
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;
const int N=3; //nbre de variables de l'EDO
///—- definition de la fonction à intégrer————————–
void lorenz( const array<double,N> &x , array<double,N> &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()
{
array<double,N> 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;
}
}
Exercice 19.0.3.
Modifier le code ci-dessus pour dessiner la trajectoire dans le plan avec un point tous les pas de temps et et obtenir l'image suivante:
Solution:
#include <iostream>
#include <boost/array.hpp>
#include <boost/numeric/odeint.hpp>
#include <TApplication.h>
#include <TCanvas.h>
#include <TMarker.h>
#include <TH1F.h>
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;
const int N=3; //nbre de variables de l'EDO
///—- definition de la fonction à intégrer————————–
void lorenz( const array<double,N> &x , array<double,N> &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()
{
TApplication theApp("App", nullptr, nullptr);
TCanvas *c = new TCanvas( "c","fenetre",500,500); // on precise la taille en pixels
double xmin(-30),ymin(0),xmax(30),ymax(50);
TH1F *h=c->DrawFrame(xmin,ymin,xmax,ymax); // coordonnees de la fenetre (optionnel, par defaut: 0-1)
h->SetXTitle("x1");
h->SetYTitle("x2");
int cpt=0;
array<double,N> x = { 10.0 , 1.0 , 1.0 }; // conditions initiales
double t1 =0 ,t2=100; // temps initial et final
double dt =0.002; //pas de temps
for(double t=t1; t<=t2; t=t+dt)
{
integrate( lorenz , x , t, t+dt, dt); // integre entre t et t+dt
// cout << t << '\t' << x[0] << '\t' << x[1] << '\t' << x[2] << endl;
cpt++;
TMarker *p=new TMarker(x[1],x[2],6);
p->Draw();
TMarker p2(x[1],x[2],8);
p2.SetMarkerColor(kRed);
p2.SetMarkerSize(2);
p2.Draw();
if(cpt%10==0)
c->Update();
}
c->Update();
theApp.Run();
}
19.1 Integration ordinaire d'une fonction
Chapitre 20 Lecture et écriture d'un fichier son (audio) de format WAV
20.0.0.1 Introduction
- Un signal audio est une fonction qui représente les variations de pression sonores en fonction du temps .
- Un fichier audio au format WAV représente le signal audio pour des valeurs discrètes du temps:avec un pas de temps appelée fréquence d'échantillonage. Les valeurs standard sont: etc. Chaque valeur est codée sur 8 bits ou 16 bits. Le nombre de caneaux 1 ou 2 indique si le fichier peut contient un (mode mono) ou deux signaux (mode stéréo). Le fichier WAV contient les valeurs à la suite, mais au début du fichier il y a les paramètres du codage. Regarder le code source pour plus d'informations.
20.0.0.2 Travail préalable pour le projet C++
- Fichiers à télécharger et sauver dans son répertoire: bib_fred.h et bib_fred.cc.
- Dans codeblocks, il faut ajouter ces fichiers au projet en faisant dans le menu: Projet/add_files ...
- Il faut aussi rajouter -larmadillo dans les options de compilation (pour codeblocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions)
20.0.1 Programme pour écrire un fichier WAV:
Pour tester, télécharger ce fichier
Voyelles_Par_Malik.wav et sauvegarder le dans le répertoire de votre projet.
Le programme suivant permet
- de lire et afficher les paramètres du codage audio de ce fichier sonore (nombre de canaux, taux d'echantillonage etc..),
- de mettre les amplitudes 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 fichier (il envoie le fichier sur la carte son).
#include <iostream>
using namespace std;
#include <TCanvas.h>
#include <TMarker.h>
#include <TApplication.h>
#include "bib_fred.cc"
///--------------------------------------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
//---- 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();
}
Sortie du programme:
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.
--------------------
20.0.2 Programme pour lire un fichier WAV:
Le programme suivant crée un signal échantilloné dans un vecteur de double (de type vec) et ensuite écrit ce vecteur dans un fichier au format WAV. De plus il lance le logiciel vlc qui permet de jouer le fichier (envoie sur la carte son).
#include <iostream>
using namespace std;
#include <TCanvas.h>
#include <TMarker.h>
#include <TApplication.h>
#include "bib_fred.cc"
///--------------------------------------------
int main()
{
TApplication theApp("App", nullptr, nullptr);
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();
}
Sortie du programme: ce fichier
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 21 Audio avec RtAudio
On explique l'utilisation de la librairie
RtAudio que l'on recommande.
21.1 Exemple
21.1.0.1 Code: fichier sinus.cc
// -*- mode:C++ ; compile-command: "g++ -Wall -D__LINUX_ALSA__ -o sinus sinus.cc ../RtAudio.cpp -lasound -lpthread" -*-
//----------------
#include "../RtAudio.h"
#include <iostream>
using namespace std;
#include <math.h>
#include <unistd.h>
#define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) )
unsigned int nchan = 2; // 1: mono, 2: stereo
int mode = 1; // 0: rec, 1: play 2:rec&play
//=============================
/*
input:
N
inbuf[i], i=0->N-1
t0 : time of inbuf[0]
output:
outbuf[i], i=0->N-1
*/
static int audio_event(void *outbuf, void *inbuf, unsigned int N, double t0, RtAudioStreamStatus status, void *userdata)
{
double *buf1 = (double*)inbuf;
double *buf2 = (double*)outbuf;
//----------------------
if(mode == 0) //record
{
if(nchan == 1) // mono
for(int i=0; i< (int)N; i= i+1)
cout<<buf1[i]<<",";
else // stereo
for(int i=0; i< 2*(int)N; i= i+2)
cout<<"("<<buf1[i]<<","<<buf1[i+1]<<"),";
}
//----------------------
if(mode == 1) //play
{
if(nchan == 1) // mono
for(int i=0; i< (int)N; i= i+1)
{
double t = t0 + i/(44100.);
buf2[i] = 0.1* sin( 2 * M_PI * 440 * t );
}
else // stereo
for(int i=0; i< 2*(int)N; i= i+1)
{
double t = t0 + i/(2*44100.);
if(i%2==0)
buf2[i] = 0.1* sin( 2 * M_PI * 440 * 3./4.* t ); // quarte
else
buf2[i] = 0.1* sin( 2 * M_PI * 440 * t );
}
}
//----------------------
if(mode == 2) //rec & play
{
if(nchan == 1) // mono
for(int i=0; i< (int)N; i= i+1)
buf2[i] = 0.1* buf1[i];
else // stereo
for(int i=0; i< 2*(int)N; i= i+1)
buf2[i] = 0.1* buf1[i];
}
return 0; // means ok
}
//==========================================
int main(void)
{
RtAudio *audio;
unsigned int bufsize = 256; // size of buffers, = 2^k.
void * data = nullptr;
audio = new RtAudio();
RtAudio::StreamParameters *inParam = nullptr;
if(mode ==0 || mode ==2)
{
inParam =new RtAudio::StreamParameters();
inParam->deviceId = audio->getDefaultInputDevice();
inParam->nChannels = nchan;
}
RtAudio::StreamParameters *outParam = nullptr;
if(mode == 1 || mode ==2)
{
outParam = new RtAudio::StreamParameters();
outParam->deviceId = audio->getDefaultOutputDevice();
outParam->nChannels = nchan;
}
audio->openStream(outParam, inParam, RTAUDIO_FLOAT64, 44100, &bufsize, audio_event, (void*) data);
audio->startStream();
sleep(20);
audio->stopStream();
audio->closeStream();
delete audio;
return 0;
}
21.1.0.2 Compilation sous Linux:
- avec ALSA:
g++ -Wall -D__LINUX_ALSA__ -o sinus sinus.cc ../RtAudio.cpp -lasound -lpthread
- ou avec PULSE-AUDIO:
g++ -Wall -D__LINUX_PULSE__ -o sinus sinus.cc ../RtAudio.cpp -lpulse-simple -lpulse -lpthread
21.1.0.3 Execution
./sinus
Chapitre 22 Gestion de la carte son (audio temps réel) 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 cat /proc/asound/cards ou aplay -l, cela donne par exemple
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
22.1
Installation (à faire la première fois)
- Si vous êtes administrateur, suivre cette documentation. Puis tester:
- Pour envoyer (écouter) le fichier test.wav sur la carte 0 (carte son par default) par exemple il faut écrire: aucat -i ~/test.wav.
- 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érifier la liste des cartes sons de votre ordi avec la commande aplay -l et:
- Pour envoyer (écouter) le fichier test.wav sur la carte 1 par exemple il faut écrire: aucat -f rsnd/1 -i ~/test.wav.
- 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". Voir remarques de configuration ci-dessous pour plus d'options.
- 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:
- 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 effet 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/Compiler/CompilerSetting/OtherOptions),
- -L/$HOME/lib et -lsndio (: dans codeblocks, c'est à rajouter dans Settings/Compiler/LinkerSettings/OtherOptions)
- Dans le fichier .bashrc il faut rajouter export LD_LIBRARY_PATH=$HOME/lib et executer dans une fenêtre de commande: . .bashrc
Autres options de configuration de sndio:
Dans le fichier /etc/default/sndio on peut ecrire par exemple:
DAEMON_OPTS="-z 128 -q rmidi/0 -q rmidi/1 -q rmidi/2 -f rsnd/1 -s default -m play,mon -s fred"
puis faire
service sndiod enable
service sndiod start
Rappel: amidi -l : enumere les ports midi utilises par les appareils"
aplay -l : detecte les cartes son -> numero 1"
Ces options signifient:
- -q : signifie configurer & exposer ce port midi pour que plusieurs programmes puissent l'utiliser"
- -z latence
- -f rsnd/1: demande utiliser carte son numero 1. L'ordre est important. plus d'info avec: xterm -e sudo pasuspender -- SNDIO_DEBUG=2 etc..
- -m play,mon -s fred : signifie redirige la sortie audio vers l'entree. Cela permet d'enregistrer ce qui est joué. Il y a une association entre les connecteurs entree et en sortie.
- -s default :permet de garder la configuration par default, cad utiliser aussi le micro. le choix du device se fait dans le programme qui utlise sndio.
22.1.0.1 Travail préalable pour le projet C++
- Fichiers à télécharger et sauver dans son répertoire: bib_fred.h et bib_fred.cc, audio.h et audio.cc.
- Dans codeblocks, il faut ajouter ces fichiers au projet en faisant dans le menu: Projet/add_files ...
- Il faut aussi rajouter -larmadillo dans les options de compilation (pour codeblocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions)
- 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: sndiod -dd -z480 -f rsnd/0 et le laisser ouvert.
22.2 Utilisation du micro (entrée) et du haut-parleur (sortie)
22.2.1 Le micro
Le micro échantillonne le son avec un pas de temps 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 donnée, soit de durée sec., et les dessine au fur et à mesure.
#include "bib_fred.cc"
#include "audio.cc"
#include <iostream>
using namespace std;
#include <TROOT.h>
#include <TApplication.h>
//===Main================================================
int main()
{
TApplication theApp("App", nullptr, nullptr);
Audio s;
s.Initialise_audio(1); // initialise la carte son en entree (1) et affiche 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
Dessin(V,"V");
// Dessin(V,"t","s",0,N*s.Dt,"V",0,"L",-1e4,1e4,kRed,0); // dessin normalisé en rouge
}
/// ----Donne la main a l'utilisateur ---
theApp.Run();
}
22.2.1.1 Exemple : détection de note de musique en temps réel
Une note de musique est un signal périodique de période , et fréquence dans l'intervalle audible . Le programme suivant utilise un algorithme de détection de période dans un signal, et affiche 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 fichier signal.cc.)
Résultat:
Periode : T=0.0034036s. <-> Frequence f=1/T=293.806 Hz
Note =Re, Ecart = 0.00833494 demitons, Octave=0.
#include "bib_fred.cc"
#include "audio.cc"
#include <iostream>
using namespace std;
#include <TROOT.h>
#include <TApplication.h>
string Notes[12] = {"Do","Do#", "Re","Re#", "Mi", "Fa", "Fa#", "Sol", "Sol#", "La", "La#", "Si"};
//===Main================================================
int main()
{
TApplication theApp("App", nullptr, nullptr);
Audio s;
s.Initialise_audio(1); // initialise la carte son en entree (1) et affiche ses informations
int N=2000; // on donne la taille que l'on veut (determine la fenetre temporelle)
vec V(N);
//parametres pour la detection de la periode
int NP=2; // 1,2: nbre de periodes à afficher
double f_A = 440; // diapason
s.seuil_norme_par_I=1e4;
s.seuil_C =0.1;
s.verbose =0; //1 affiche messages erreur
//---------------------
while(1) // boucle infinie
{
s.Lit_donnees_audio(V); // remplit V depuis le micro
double t0 =0; // date de debut d'analyse
int opt_dessin_C=0; // 0 ou 1
double T=s.Detecte_Periode(V, t0, opt_dessin_C); // -> Periode T ou 0
if(T>0)
{
double f=1/T;
cout<<" Periode : T="<<T<<"s. <-> Frequence f=1/T="<<f<<" Hz"<<endl;
//--- conversion en note de musique
double nu = 12*log(f/f_A)/log(2.) + 69;
int nu_i = floor(nu+0.5);
cout<<" Note ="<<Notes[nu_i%12]<<" Ecart = "<<nu-nu_i<<" demitons"<<" Octave="<<nu_i/12-5<<endl;
//---- affiche NP periodes extraites
vec Extrait = V.rows( t0/s.Dt, (t0+NP*T)/s.Dt); // on extrait NP periodes
uword i = Extrait.index_max();
Extrait =shift(Extrait, -i ); // place le max de l'extraitau debut
Dessin(Extrait,"t","s",0,NP*T,"Extrait",0,"L",-1111,-1111,kRed,0); // dessin normalisé
}
}
/// ----Donne la main a l'utilisateur ---
theApp.Run();
}
22.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.
#include "bib_fred.cc"
#include "audio.cc"
#include <iostream>
using namespace std;
#include <TROOT.h>
#include <TApplication.h>
int main()
{
TApplication theApp("App", nullptr, nullptr);
Audio s;
s.Initialise_audio(3); // initialise la carte son en entree et sortie (3) et affiche ses informations
vec V;
while(1) // boucle infinie
{
s.Lit_donnees_audio(V); // micro -> V
Dessin(V,"t","s",0,V.size()*s.Dt,"V",0,"L",-1e4,1e4,kRed,0); // dessin de V
s.Ecrit_donnees_audio(V); // V -> Haut Parleur
}
/// ----Donne la main a l'utilisateur ---
theApp.Run();
}
22.3 Change pitch or tempo of an audio file
Chapitre 23 Threads: plusieurs programmes en parallèle qui communiquent
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 fini 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:
- 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.
- 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 afin 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).
- 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.
23.1 Lancement de threads ou processus
23.1.1 Exemple
23.1.1.1 Commande de compilation:
- Si on utilise codeblocks, il faut rajouter -pthread -std=c++11 dans Settings/Compiler/Compiler_Settings/Other_Options et dans Settings/Compiler/Linker_Settings/Other_linker_Options.
- Pour une compilation en ligne de commande:
g++ main.cpp -o main.out -pthread -std=c++11
23.1.1.2 Code c++, fichier main.cpp
#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";
}
Résultat:
Les fonctions main et f sont maintenant en concurrence.
bbbbbaaaaaaaaaabbbbb
f est finie.
23.1.1.3 Commentaires
- Les fonctions f et main fonctionnent en parallèle et leur affichages 'a' ou 'b' sur l'écran se mélangent de façon incontrollée. Un deuxième lancement du programme donnerait éventuellement autre chose. Si cela est génant, on verra plus dans l'exemple 23.3.2, une solution pour éviter ce mélange.
- On peut passer (ou pas) des variables à la fonction lancée en parallèle, ici on passe la variable n.
- On peut passer un pointeur (adresse d'une variable) à la fonction 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.
- 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.
- Il peut être utile de créer une certaine attente dans un programme. Utiliser Section 17.2.
- Si vous oubliez t.join() à la fin, il y aura une erreur à l'execution du programme.
- Documentation complète sur thread.
23.1.2 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=" << n << endl; }
};
int main()
{
A a;
thread t1(&A::f, &a, 100);
t1.join();
}
Résultat:
n=100
Commentaires
- Si on lance le thread depuis une autre fonction membre, il faut bien sûr remplacer le pointeur &a par this.
23.1.3 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
}
Résultat:
315524563656666254443
23.1.4 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()
{
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
}
Résultat:
213332444456666665555
23.2 Partage de variables communes avec “atomic”
Si une variable est utilisée par deux threads différents qui fonctionnent en parallèle, alors il est important que l'accès en écriture/lecture soit fait de façon “atomique” c'est à dire que si un thread utilise la variable, cela bloque l'accès pour les autres threads.
Exemple:
#include <iostream> // std::cout
#include <atomic> // std::atomic, std::memory_order_relaxed
#include <thread> // std::thread
using namespace std;
atomic<int> X(0);
//----------------------------
void set(int x)
{
X.store(x, memory_order_relaxed); // set value atomically
}
//------------------------
void print()
{
int x;
do
{
x = X.load( memory_order_relaxed); // get value atomically
cout << "x=" << x <<endl;
}
while (x==0);
cout << "X=" << x <<endl;
}
//------------------
int main ()
{
thread first(print);
thread second(set, 10);
first.join();
second.join();
return 0;
}
Résultat:
x=0
x=10
X=10
23.3 Mutex pour annoncer occupé/libre
23.3.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 modifier une variable commune à plusieurs threads. On déclare un mutex par
mutex mtx;
Ensuite il y a deux commandes de base:
- La commande
mtx.lock();
qui signifie:
- si mtx est “libre” on le met à “occupé” tout de suite et on continue,
- sinon, si mtx est “occupé” alors on attend jusqu'à ce qu'il soit libre, on le met à “occupé” et on continue,
- La commande
mtx.unlock();
qui signifie: on met mtx à “libre”
Remarques
- Question: Quel intéret d'utiliser un mutex mtx plutot qu'une variable boolenne mtx ordinaire pour effectuer la suite d'instructions “si mtx est “libre” on le met à “occupé” et on continue, sinon on attend”?
réponse: en utilisant une variable ordinaire, il y a le risque qu'un autre processus viennent modifier 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”).
23.3.2
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();
}
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.
23.3.3 Autres utilisations des mutex
- Pour bloquer plusieurs mutex simultanément: Doc on scopped_lock, “A tour of c++”, 2018, page 418:
scoped_lock lck {mutex1,mutex2,mutex3};
- Pour signaler la présence du thread à l'aide d'un mutex, mais en acceptant d'autres threads: (par exemple on veux utiliser de la mémoire en lecture seule, donc on accepte que d'autres threads puissent lire aussi)
shared_mutex mx;
- Pour exiger d'être seul à utiliser le mutex: (par exemple on veux utiliser de la mémoire en écriture, donc on n'accepte pas que d'autres threads puissent lire ou écrire aussi)
unique_lock lck {mx};
23.4 Communications: variables conditionnelles pour réveiller un programme en attente
23.4.1 Introduction
Dans la communication entre programmes (process), il peut arriver qu'un programme “2” attende le résultat 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 effectue une boucle infinie 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 conditionelle.
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.)
23.4.2 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.
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();
}
Résultat:
On attend 2sec.
voilà.
FIN
23.4.2.1 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 23.4.5 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écifie 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.
23.4.3 “où un thread endormi attend des évènements provenant de differents thread extérieurs”.
#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();
t2.join();
}
Résultat:
1,2,1,1,2,1,2,1,1,2,1,2,1,1,2,1,2,1,1,2,...
- Pour expliquer le résultat, il faut remarquer que la fonction f1() affiche “1” aux dates suivantes (en ms): 1000, 2000, 3000, 4000, 5000,.... Après chaque affichage, la fonction f1() reveille la fonction main() qui affiche “,”. De même, la fonction f2() affiche “2” aux dates: 1502, 3004, 4506, 6008, ... et de même à chaque affichage elle reveille la fonction main() qui affiche “,”. 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 reveiller un seul (on ne sait pas lequel). Par contre la commande cv.notify_all(); reveillerait tous les threads mis en attente.
23.4.4 “où deux thread endormi sont reveillés par une notification 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 mtx. Il sera debloque automatiquement par wait(lck), ou a la destruction de lck.
cout<<"a"<<endl;
cv.wait(lck);
cout<<"b"<<endl;
}
//——————–
void f2()
{
unique_lock<mutex> lck(mtx); // bloque le mutex mtx. Il sera debloque automatiquement par wait(lck), ou a la destruction de lck.
cout<<"A"<<endl;
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();
}
Résultat:
a
A
3,2,1,0,partez!
b
B
23.4.5
“Un thread remplit une liste. Le deuxieme thread vide la liste”.
#include <iostream>
using namespace std;
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <list>
mutex mtx;
condition_variable cv;
list<int> L;
int N=10;
//--------------------
void f()
{
while(1) // boucle infinie
{
{
unique_lock<mutex> lck(mtx); // bloque le mutex mtx. Il sera debloque par wait(lck) ou a la destruction de lck
if(L.size()< N) // si liste non pleine
cv.wait(lck); // debloque le mutex lck et met le thread en attente (endormi), de facon atomique. Il sera réveillé par un signal extérieur venant de cv.notify_one();
} // ici lck est detruit donc mtx est debloque
mtx.lock();
cout<<"Liste pleine. On vide la liste par le debut:"<<flush;
while(L.size()>0)
{
cout<<L.front()<<","<<flush;
this_thread::sleep_for(chrono::milliseconds {100});
L.pop_front(); // enleve le premier element
}
cout<<endl;
mtx.unlock();
cv.notify_one(); // reveille le thread endormi
}
}
//--------------------
int main()
{
thread t(f); // lance la fonction f dans un thread
while(1) // boucle infinie
{
mtx.lock(); // bloque le mutex
if(L.size()==0) // si liste vide
{
cout<<"Liste Vide. Remplit la liste:"<<flush;
for(int j=0; j< N; j++)
{
cout<<j<<","<<flush;
L.push_back(j);
this_thread::sleep_for(chrono::milliseconds {100});
}
cout<<endl;
}
mtx.unlock(); // debloque
cv.notify_one(); // reveille le thread endormi
// se met en attente
unique_lock<mutex> lck(mtx);
cv.wait(lck);
}
//.........
t.join();
return 0;
}
Résultat:
Liste Vide. Remplit la liste:0,1,2,3,4,5,6,7,8,9,
Liste pleine. On vide la liste par le debut:0,1,2,3,4,5,6,7,8,9,
Liste Vide. Remplit la liste:0,1,2,3,4,5,6,7,8,9,
Liste pleine. On vide la liste par le debut:0,1,2,3,4,5,6,7,8,9,
...
23.5 Async()
Pour lancer facilement plusieurs calculs en parallèle et collecter les résultats.
#include <iostream>
using namespace std;
#include <chrono>
using namespace std::chrono;
#include <future>
//-----------------------
double calculus(double x, double y)
{
this_thread::sleep_for(chrono::seconds {2});
return x*y;
}
//--------------------------
int main()
{
auto t1=high_resolution_clock::now(); // mesure du "time point 1"
auto a1 = async(calculus,3,3); // thread 1
auto a2 = async(calculus,4,4); // thread 2
auto a3 = async(calculus,5,5); // thread 3
auto t2=high_resolution_clock::now(); // mesure du "time point 2"
auto dt = duration_cast<duration<double>>(t2-t1).count(); // calcul de la durée en ms
cout<<"3*3+4*4-5*5="<< a1.get() + a2.get()- a3.get()<<", calcul en "<<dt<<" ms"<<endl;
}
Résultat:
3*3+4*4-5*5=0, calcul en 2.24e-06 ms
Chapitre 24 CGI: communication avec un serveur web
Le CGI permet de communiquer entre deux ordinateurs sur le réseau internet via un
navigateur web. Un ordinateur est
serveur et l'autre est
client. Le client envoit des données au serveur. Le serveur les recoit sur le flux en entrée
cin<<... renvoit en réponse d'autres données au client sur le flux en sortie
cout>>....
Dans les exemples qui vont suivre
- l'ordinateur client sera toujours notre propre ordinateur et utilise la methode POST pour envoyer des données au serveur (voir HTTP methods)
- L'ordinateur serveur peut être un autre ordinateur (ex: serveur du labo) ou notre propre ordinateur configuré en serveur. Ce dernier cas peut être utile pour faire des test, avant de “publier” le programme sur le serveur.
Habituellement, le flux en entrée est traité (“parsé”) par les fonctions d'une librairie spécifique afin de bien les recevoir. Le flux de sortie est envoyé au format html pour qu'il s'affiche ainsi dans le navigateur. L'ordinateur qui joue le role de serveur a besoin d'être configuré spécifiquement.
24.1 Exemple 1
On va décrire cet
exemple. Son execution ecrit un résultat sur la page web du client mais aussi dans ce
fichier, sur le serveur.
On décrit d'abord l'utilisation sur un serveur local.
24.1.1 Programme html du client
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form method="post" action="http://localhost:8080/cgi-bin/cgi_test1_local.cgi">
Your name : <input type="text" name="nom" /><br />
Your age : <input type="text" name="age" /><br />
<input name="envoi" type="submit">
</form>
</body>
</html>
24.1.2 Programme C++ du serveur utilisant CGI
Ce sera un programme C++ qui va tourner sur l'ordinateur serveur et donc en principe il peut faire beaucoup de choses:
- Principalement il fait des calculs et envoit ses données au navigateur client en écrivant au format html sur le flux de sortie, i.e. avec cout<<...
- Il peut aussi exécuter des scripts
- accéder aux fichiers de l'ordinateur serveur (lecture/écriture)
- On va utiliser la librairie cgicc pour écrire le programme du serveur en C++.
// -*- mode:C++ ; compile-command: "/home/faure/c++/essais/cgi_/exemple_1/script_de_demarrage_test1_local" -*-
/*
1er Test de cgi de (cgicc)
(exemple plus complet: voir animations_web.cc"
Compilation
--------------------
sur serveur labo:
script_de_demarrage_test1_labo
en local (pas encore au point, revoir le script):
script_de_demarrage_test1_local
Infos
--------------
au labo
---------
le programme cgi peut lire/ecrire des fichiers,
dans le repertoire local
/faure/
qui est accessible depuis l'exterieur par
https://www-fourier.ujf-grenoble.fr/~fauretmp/
En local:
---------------
Pour un serveur local (maison) , les fichiers peuvent etre mis dans cgi-file/temp/
rem: il faut avoir mit le repertoire en permission ecriture for all
*/
#include <iostream>
using namespace std;
#include <cgicc/Cgicc.h> // pour recup�rer l'info des entr�es
#include <cgicc/HTTPHTMLHeader.h> // pour r�cup�rer infos sur les variables d'environnement
#include <cgicc/HTMLClasses.h>//
using namespace cgicc;
#include <string>
//-----------------------
int main(int argc, char **argv)
{
try {
Cgicc cgi;
//---- entete du document HTML de sortie ----------------
cout << HTTPHTMLHeader() << endl;
cout << html() << head(title("Cgicc example 1")) << endl;
cout << body() << endl;
//---- On recupere les donnees des widgets
//...... version simple sans test
string nom = cgi("nom"); // l'element "nom" est defini dans le fichier html qui a declench� ce programme
string age = cgi("age");
cout << "Votre nom est: " << nom <<" age = "<<age<<"<br>"<< endl; // idem
//...... version avec plus de tests, en particulier si le nom du widget existe -------
form_iterator name = cgi.getElement("nom");
if(! name-> isEmpty() && name != cgi.getElements().end())
{
cout << "Je repete, votre nom est: " << name->getValue() <<" <br>"<< endl; // idem
}
else
cout<<"Erreur..<br>"<<endl;
//--------------
cout<<endl<<"Script cgi, le 01/12/2020 par c++/essai/cgi_/exemple_1/cgi_test1.cc utilisant la biblioth�que Cgicc"<<endl;
cout << body() << html(); //Close the HTML document
//----- write in a file on the serveur
string nom_fichier = "/faure/essai.txt"; // si serveur labo
// string nom_fichier = "../cgi-file/temp/essai.txt"; // si serveur local, ne marche pas?
ofstream f;
f.open(nom_fichier.c_str(), ios_base::out); // app: rajoute � la suite, out: commence � z�ro
f << "Nom = " << nom << endl;
f.close();
}
//------------------
catch(exception& e)
{
cout<<"Un probleme..."<<endl;
// handle any errors - omitted for brevity
}
}
24.1.3 Script de compilation et lancement sur serveur local
#nom de ce fichier: script_de_demarrage_test1_local
#compilation:
g++ cgi_test1.cc -o cgi_test1 -lm -lcgicc
#copie l'executable dans le rep cgi-bin:
cp /home/faure/c++/essais/cgi_/exemple_1/cgi_test1 ~/public_html/cgi-bin/cgi_test1_local.cgi
#copie le fichier interface html dans le repertoire cgi-file:
cp ~/c++/essais/cgi_/exemple_1/cgi_test1_local.html ~/public_html/cgi-file/
#lance un serveur web dans un autre terminal console, meme repertoire que ce projet:
cd ~/public_html/
xterm -e python -m CGIHTTPServer 8080 &
# ouvre fichier html
firefox http://localhost:8080/cgi-file/cgi_test1_local.html
24.1.4 Pour utiliser le serveur distant (du labo)
Il faut changer ce qui précède par ces fichiers
24.1.4.1 Fichier html:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form method="post" action="https://www-fourier.ujf-grenoble.fr/~faure/cgi-bin/cgi_test1_labo.cgi">
Your name : <input type="text" name="nom" /><br />
Your age : <input type="text" name="age" /><br />
<input name="envoi" type="submit">
</form>
</body>
</html>
24.1.4.2 Fichier script:
#nom de ce fichier: script_de_demarrage_test1_labo
#------compilation. Rem l'option -static permet d'embarquer les librairires comme lib statiques et donc que le code soit executable sur le serveur du labo.
g++ cgi_test1.cc -o cgi_test1 -lm -lcgicc -static
#copie l'executable dans le rep cgi-bin:
cp /home/faure/c++/essais/cgi_/exemple_1/cgi_test1 ~/public_html/cgi-bin/cgi_test1_labo.cgi
#copie le fichier interface html dans le repertoire cgi-file:
cp ~/c++/essais/cgi_/exemple_1/cgi_test1_labo.html ~/public_html/cgi-file/
#--------------copie le repertoire public_html sur le serveur du labo -------------------------
rsync -aLvp --delete -e ssh -P --exclude "error.log" --exclude 'cgi_requests' /home/faure/public_html/ malherbe.ujf-grenoble.fr:/home/faure/public_html
#lance le navigateur pour utiliser le programme
firefox https://www-fourier.ujf-grenoble.fr/~faure/cgi-file/cgi_test1_labo.html
24.2 Autres remarques, variantes
24.2.1 Le Client peut envoyer des données directement au serveur
Dans le code html du client:
<form method="post" action="https://www-fourier.ujf-grenoble.fr/~faure/cgi-bin/cgi_test1.cgi?toto">
On a rajouté ?toto à la fin. Cela a pour effet d'envoyer la chaine de caractères toto qui sera récupérée de la façon suivante dans le programme C++ du serveur
int main(int argc, char **argv)
{
string chaine
if(argc>=2)
chaine = argv[1];
...
24.2.2 Envoi des données par Javascript
24.2.2.1 Avec Ajax
References:
Remarque importante sur les permissions:
Il est indispensable que le fichier html soit télécharger depuis le même serveur que celui à qui on envoit la requète POST. Sinon il y a une erreur qui apparait dans la console: “(Reason: CORS header ‘Access-Control-Allow-Origin’ missing)”.
Exemple:
Remplacer les fichier html de l'exemple 1 par le fichier suivant:
<!DOCTYPE html>
<html>
<body>
<h2>Send data POST by JavaScript</h2>
<button type="button" onclick="f()">Action</button>
<p id="demo"></p>
<script>
//---- this function is called by the button 'Action'
function f()
{
var xhttp = new XMLHttpRequest();
//---- send data to the serveur
// xhttp.open("POST", "http://localhost:8080/cgi-bin/cgi_test1_local.cgi", true); // local
xhttp.open("POST", "https://www-fourier.ujf-grenoble.fr/~faure/cgi-bin/cgi_test1_labo.cgi", true); // labo
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); //send a header
xhttp.send("nom=Henry&age=12"); // send data forms
//---- this function is called after serveur's answer
xhttp.onreadystatechange = function()
{
if (this.readyState == 4 && this.status == 200) // 4: finished, 200: OK
{
document.getElementById("demo").innerHTML = this.responseText; // responseText: recieve a string
}
};
} // f()
</script>
</body>
</html>
24.2.2.2 Avec JQuery
24.2.3 Code pour forcer le navigateur du client à ne pas garder de cache
cout <<"<META http-equiv=\"Cache-Control\" content=\"no-cache\"> "<<endl;
cout <<"<META http-equiv=\"Pragma\" content=\"no-cache\">"<<endl;
cout <<"<META http-equiv=\"Expires\" content=\"0\"> "<<endl;
24.2.4 Envoyer un fichier du client vers le serveur
Code html Client:
<html>
<body>
<form enctype="multipart/form-data" method="post" action="https://www-fourier.ujf-grenoble.fr/~faure/cgi-bin/cgi_test1.cgi">
<input type="file" name="the_file"></input>
<input type="submit" name="submit" value="send the file"></input>
</form>
</body>
</html>
Code C++ serveur: attention il faut rajouter une sécurité de
timeout afin que le programme ne reste bloqué.
#include <stdio.h>
int main(char ** argv, int argc)
{
FILE * fp=fopen("/faure/essai.txt", "wb" );
char tmp[1024];
int nRead;
while (nRead=fread(tmp, 1, 1024, stdin))
{
fwrite(tmp, 1, nRead, fp);
}
fclose(fp);
return 0;
}
Chapitre 25 Sockets: communications entre process via le réseau
Référence:
25.1 Réseaux
- Adresse MAC/ adresse IPV4.
- IPV6: convention plus récente mais peu utilisée, déconseillé pour le moment.
- wireshark: logiciel qui permet d'observer le réseau.
- On met la carte reseau dans un mode appelé “promicious”, elle écoute tous les paquets qui passent.
- Utile pour la pédagogie.
25.2 Introduction
Le but d'un socket est d'échanger des données (des octets) entre deux processus appelés client et serveur qui tourner sur deux ordinateurs différents.
Il y a 4 types de sockets:
- Stream socket (Transmission Control Protocol, TCP) : garantit que l'échange est bien effectué (renvoit un code d'erreur sinon).
- Datagram socket (User Datagramm Protocol, UDP): aucune garantit. C'est comme lancer une bouteille à la mer. C'est utile pour signaler la présence d'un process.
- Raw socket: pour développeurs.
- Sequenced packet sockets: idem stream sockets, mais on peut envoyer un nombre limité de données.
Il y a deux types de communication entre un client et serveur:
- 2-tier: communication: client<->serveur directe. Cela peut être risqué.
- 3-tier: communication via un “middle-ware” qui vérifie les messages avant de les transmettre: client<->middle<->serveur
25.2.1 Adresses IP
Les ordinateurs sont repérés par un code appelé
adresse IP (Internet Protocol) codée sur 4 octets:
avec les conventions suivantes.
25.2.1.1 Classes d'adresses
Selon la valeur de , (en base 2 et signifie une valeur quelconque):
- Sous classe de A:réservée aux adresses internes à un ordinateur, appelées loopback. Ex: écrire ping 127.0.0.1 dans un terminal.
- Sous réseau: pour une adresse de classe B, caractérisent le réseau, caractérise le sous réseau, caractérise la machine “host”
Noms associés:
à une adresse IP, on peut associer un ou plusieurs noms (en string). Une liste d'association est dans le fichier /etc/hosts de l'ordinateur.
25.2.2 Résumé des fonctions et structures utilisées
25.2.2.1 Fonctions
Pour effectuer une connection entre un client et un serveur via un socket, voici les étapes:
- Commun aux client et serveur: details here
- int socket (int family, int type, int protocol); // to establish a connection with a TCP server.
- family: specifies the protocol family ,ex: AF_INET for IPv4 protocols
- type: specifies the kind of socket you want. ex: SOCK_STREAM for Stream socket
- protocol: specific protocol, ex: IPPROTO_TCP for TCP transport protocol
- int send(int sockfd, const void *msg, int len, int flags); //to send data over stream sockets or CONNECTED datagram sockets
- This call returns the number of bytes sent out, otherwise it will return -1 on error.
- sockfd − It is a socket descriptor returned by the socket function.
- msg − It is a pointer to the data you want to send.
- len − It is the length of the data you want to send (in bytes).
- flags − It is set to 0.
- int recv(int sockfd, void *buf, int len, unsigned int flags); //to receive data over stream sockets or CONNECTED datagram sockets
- returns the number of bytes read into the buffer, otherwise it will return -1 on error.
- sockfd − It is a socket descriptor returned by the socket function.
- buf − It is the buffer to read the information into.
- len − It is the maximum length of the buffer.
- flags − It is set to 0.
- Rem: sendto() and recvfrom() , idem, but for UNCONNECTED datagram sockets.
- int write(int fildes, const void *buf, int nbyte); //pour envoyer des données
- returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.
- fildes − It is a socket descriptor returned by the socket function.
- buf − It is a pointer to the data you want to send.
- nbyte − It is the number of bytes to be written. If nbyte is 0, write() will return 0 and have no other results if the file is a regular file; otherwise, the results are unspecified.
- int read(int fildes, const void *buf, int nbyte); //pour recevoir des données
- returns the number of bytes actually written to the file associated with fildes. This number is never greater than nbyte. Otherwise, -1 is returned.
- fildes − It is a socket descriptor returned by the socket function.
- buf − It is the buffer to read the information into.
- nbyte − It is the number of bytes to read.
- int close( int sockfd ); // ferme la connexion
- sockfd − It is a socket descriptor returned by the socket function.
- rem: int shutdown(int sockfd, int how); // idem but gives more control
- int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout); // indicates which of the specified file descriptors is ready for reading, ready for writing, or has an error condition pending. More details here
- int fork(void); //creates a new process , an exact copy of the calling process (parent process).
- returns 0 to the child process and the process ID of the child process to the parent process. Otherwise -1 is returned to the parent process, no child process is created and errno is set to indicate the error.
- Le client:
- int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); // pour se connecter à une adresse de serveur TCP
- sockfd − It is a socket descriptor returned by the socket function.
- serv_addr − It is a pointer to struct sockaddr that contains destination IP address and port.
- addrlen − Set it to sizeof(struct sockaddr).
- Le serveur:
- int bind(int sockfd, struct sockaddr *my_addr,int addrlen); // pour s'attribuer une adresse IP et port
- returns 0 if it successfully binds to the address, otherwise it returns -1 on error.
- sockfd − It is a socket descriptor returned by the socket function.
- my_addr − It is a pointer to struct sockaddr that contains the local IP address and port.
- addrlen − Set it to sizeof(struct sockaddr).
- A 0 value for port number means that the system will choose a random port, and INADDR_ANY value for IP address means the server's IP address will be assigned automatically.
- set a port above 1024 and below 65535 unless they are the ones being used by other programs.
- int listen(int sockfd,int backlog); // attentif aux demandes de connection des clients. converts an unconnected socket into a passive socket
- returns 0 on success, otherwise it returns -1 on error.
- sockfd − It is a socket descriptor returned by the socket function.
- backlog − It is the number of allowed connections.
- int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); // accepte une connection de client. Cette fonction est bloquante, en attendant la connection du client.
- sockfd − It is a socket descriptor returned by the socket function.
- cliaddr − It is a pointer to struct sockaddr that contains client IP address and port.
- addrlen − Set it to sizeof(struct sockaddr).
25.2.2.2 Structures utilisées
struct sockaddr:
struct sockaddr
{
unsigned short sa_family; // spécifie le type d'adresse, parmi AF_INET, AF_UNIX, AF_NS, AF_IMPLINK. Le plus utilisé est AF_INET.
char sa_data[14]; // adresse
};
struct sockaddr_in :
struct sockaddr_in
{
short int sin_family;// comme sa_family
unsigned short int sin_port; //numero de port
struct in_addr sin_addr; // adresse IP
unsigned char sin_zero[8]; //not used
};
struct in_addr:
struct in_addr
{
unsigned long s_addr; // IP adresse
};
struct hostent
struct hostent
{
char *h_name; // name of the host, ex: google.com
char **h_aliases; //list of host name aliases.
int h_addrtype; // = AF_INET : adresse family
int h_length; // =4 , length of the IP address
char **h_addr_list // h_addr_list[j], j=0,1.. are points to structure in_addr.
#define h_addr h_addr_list[0] // for backward compatibility
};
struct servent:
struct servent
{
char *s_name; // This is the official name of the service. For example, SMTP, FTP POP3, etc.
char **s_aliases; // list of service aliases
int s_port; // associated port number. For example, for HTTP, this will be 80.
char *s_proto; // protocol used. Internet services are provided using either TCP or UDP.
};
Remarks: Always, set the structure variables to NULL (i.e., '\0') by using memset() for bzero() functions, otherwise it may get unexpected junk values in your structure.
25.2.3 Ports
La liste des ports utilisés et leur noms se trouve dans le fichier /etc/services
25.2.3.1 Fonctions utilisées:
- struct servent *getservbyname(char *name, char *proto)
- This call takes service name and protocol name, and returns the corresponding port number for that service.
- struct servent *getservbyport(int port, char *proto)
- This call takes port number and protocol name, and returns the corresponding service name.
Ces fonctions renvoient une structure:
struct servent {
char *s_name; // value = http ,It is the official name of the service. For example, SMTP, FTP POP3, etc.
char **s_aliases; // value = ALIAS It holds the list of service aliases. Most of the time, it will be set to NULL.
int s_port; // value = 80 It will have the associated port number. For example, for HTTP, it will be 80.
char *s_proto; // value = TCP, UDP It is set to the protocol used. Internet services are provided using either TCP or UDP.
};
25.2.4 Codage sur plusieurs octets
Attention que selon l'ordinateur, on peut avoir “little/big endian”, cf
this page.
Pour les sockets il faut utiliser la convention “Network Byte Order”
25.2.5 Exemple
Voici le fichier server.c
// Server side C/C++ program to demonstrate Socket programming
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#define PORT 8080
int main(int argc, char const *argv[])
{
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char *hello = "Hello from server";
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
// Forcefully attaching socket to the port 8080
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,
&opt, sizeof(opt)))
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons( PORT );
// Forcefully attaching socket to the port 8080
if (bind(server_fd, (struct sockaddr *)&address,
sizeof(address))<0)
{
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0)
{
perror("listen");
exit(EXIT_FAILURE);
}
if ((new_socket = accept(server_fd, (struct sockaddr *)&address,
(socklen_t*)&addrlen))<0)
{
perror("accept");
exit(EXIT_FAILURE);
}
valread = read( new_socket , buffer, 1024);
printf("%s\n",buffer );
send(new_socket , hello , strlen(hello) , 0 );
printf("Hello message sent\n");
return 0;
}
Voici le fichier client .c
// Client side C/C++ program to demonstrate Socket programming
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define PORT 8080
int main(int argc, char const *argv[])
{
int sock = 0, valread;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[1024] = {0};
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// Convert IPv4 and IPv6 addresses from text to binary form
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0)
{
printf("\nInvalid address/ Address not supported \n");
return -1;
}
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
printf("\nConnection Failed \n");
return -1;
}
send(sock , hello , strlen(hello) , 0 );
printf("Hello message sent\n");
valread = read( sock , buffer, 1024);
printf("%s\n",buffer );
return 0;
}
Compiling:
gcc client.c -o client
gcc server.c -o server
Execution:
in a terminal 1:
./server
in a terminal 2:
./client
Output:
Client:Hello message sent
Hello from server
Server:Hello from client
Hello message sent
Chapitre 26 Messages MIDI Musical temps réel 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.
Autres librairies de midi temps réel:
26.1
Installation (à faire la première fois)
- Suivre les instructions de la Section 22.1.
- De plus il faut télécharger les fichiers suivants et les sauver dans son répertoire: midi.h et midi.cc.
- Si vous utilisez codeblocks, il faut ajouter ces fichiers au projet en faisant dans le menu: Projet/add_files ...
- Si vous utilisez un Makefile, rajouter les fichiers au projet selon les explications de la Section 14.
- Il faut aussi rajouter -lsndio dans les options de compilation (pour codeblocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions)
26.2 Exemple élémentaire sur l'envoi et reception de messages MIDI
Ce premier exemple montre comment envoyer des octets (chiffres) entre deux programmes via un port midi virtuel de l'ordinateur. En pratique les messages MIDI ont un contenu très particulier qui ont une signification 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 Makefile 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 test_midi_in se met en attente et lorsqu'il recoit un message midi (provenant du programme test_midi_out) il l'affiche à l'écran:
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 affiche:
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 Makefile permet de compiler les deux programmes par l'instruction make all.
//===== Programme test_midi_in.cc ==========
#include <iostream>
using namespace std;
#include "midi.h"
int main()
{
Midi midi;
//--- ouvre un port midi en entree
int port=0; // numero de port
int res = midi.Initialise_port_midi_in(port);
if(res==0)
cout<<"port est encore ferme."<<endl;
else
cout<<"port ouvert."<<endl;
//--- attente de messages
cout<<"Attente de message .."<<endl;
midi.Attente_Message();
cout<<"Messages recus."<<endl;
//--- on affiche les messages recus dans vector<vector<unsigned char>> midi.L_message
for(auto message : midi.L_message) // analyse chacun des messages midi entrant. Rem: en general il y a 1 seul message.
{
int port_in=(int)message[0]; // numero de port entrant
message.erase(message.begin()); // enleve le 1er octet. Il reste le message midi standard
cout<<"Message recu sur le port="<<port_in<<": "<<flush;
for(int i=0;i<message.size();i++)
cout<<(int)message[i]<<", "<<flush;
cout<<endl;
}
//--- ferme port midi
midi.Ferme_port_midi_in(port);
}
//===== Programme test_midi_out.cc ==========
#include <iostream>
using namespace std;
#include "midi.h"
int main()
{
Midi midi;
//--- ouvre port midi out
int port = 0;
int res = midi.Initialise_port_midi_out(port);
if(res==0)
{
cout<<"port est encore ferme."<<endl;
exit(1);
}
else
cout<<"port ouvert."<<endl;
//---- envoie message: 144, 60, 50
vector<unsigned char> message;
message.push_back(144);
message.push_back(60);
message.push_back(50);
midi.Envoi_Message(port, message);
cout<<"J'ai envoye le message 144,60,50 sur le port "<<port<<endl;
//-- ferme les ports midi ouverts
midi.Ferme_port_midi_out(port);
}
#=== Fichier Makefile
LIBR= -lm -lpthread -lsndio -std=c++11
CC =g++ -std=c++11
#==========================================
OBJETS_MIDI_O= test_midi_out.o midi.o
midi.o : midi.cc midi.h
$(CC) $(CFLAGS) -c $*.cc -o $@
test_midi_out.o : test_midi_out.cc bib_fred.h
$(CC) $(CFLAGS) -c $*.cc -o $@
test_midi_out: $(OBJETS_MIDI_O)
$(CC) -o test_midi_out $(OBJETS_MIDI_O) $(LIBR)
#==========================================
OBJETS_MIDI_I= test_midi_in.o midi.o
midi.o : midi.cc midi.h
$(CC) $(CFLAGS) -c $*.cc -o $@
test_midi_in.o : test_midi_in.cc bib_fred.h
$(CC) $(CFLAGS) -c $*.cc -o $@
test_midi_in: $(OBJETS_MIDI_I)
$(CC) -o test_midi_in $(OBJETS_MIDI_I) $(LIBR)
#==========================
all : test_midi_out test_midi_in
26.3 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.
Travail préalable
- Installer un synthétiseur MIDI comme midisyn et le lancer. Il sera alors à l'écoute du port midithru/0 qui est le port 0 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 fichier 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.cc ==========
#include <iostream>
using namespace std;
#include "midi.h"
int main()
{
Midi midi;
//--- ouvre port midi out
int port = 0;
int res = midi.Initialise_port_midi_out(port);
if(res==0)
{
cout<<"port est encore ferme."<<endl;
exit(1);
}
else
cout<<"port ouvert."<<endl;
//.....
int ch = 0; // canal
int inst = 0; // instrument, 0: piano
midi.Program_Change(port, ch, inst);// associe l'instrument inst au canal ch
for(int key=60; key<=72; key++) //montee de la gamme chromatique
{
//....... joue une note midi ..........
int vol =100;
cout<<"joue note "<<key<<endl;
midi.Joue_note(port, ch, key, vol); // message pour jouer une note, touche key
midi.Sleep(200); // attente en ms
//.... eteind une note midi .........
midi.Eteind_note(port, ch, key); // message pour eteindre la touche key
}
//-- ferme les ports midi ouverts
midi.Ferme_port_midi_out(port);
}
#====== Fichier Makefile====================================
OBJETS_MIDI_G= gamme.o midi.o
midi.o : midi.cc midi.h
$(CC) $(CFLAGS) -c $*.cc -o $@
gamme.o : gamme.cc
$(CC) $(CFLAGS) -c $*.cc -o $@
gamme: $(OBJETS_MIDI_G)
$(CC) -o gamme $(OBJETS_MIDI_G) $(LIBR)
Exemple de piano avec percussions
//===== Programme gamme.cc ==========
#include <iostream>
using namespace std;
#include "midi.h"
int main()
{
Midi midi;
//--- ouvre port midi out
int port = 0;
int res = midi.Initialise_port_midi_out(port);
if(res==0)
{
cout<<"port est encore ferme."<<endl;
exit(1);
}
else
cout<<"port ouvert."<<endl;
//.....
int ch = 0; // canal
int inst = 0; // instrument, 0: piano
midi.Program_Change(port, ch, inst);// associe l'instrument inst au canal ch
for(int key=60; key<=72; key++) //montee de la gamme chromatique
{
//....... joue une note midi ..........
int vol =100;
cout<<"joue note "<<key<<endl;
midi.Joue_note(port, ch, key, vol);
//... ride
midi.Joue_note(port, 9, 53, vol);
midi.Sleep(200); // attente en ms
//... stick
midi.Joue_note(port, 9, 37, vol);
midi.Sleep(200); // attente en ms
//.... eteind une note midi .........
midi.Eteind_note(port, ch, key);
}
//... splash
midi.Joue_note(port, 9, 55, 100);
//-- ferme les ports midi ouverts
midi.Ferme_port_midi_out(port);
}
26.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 off”, il affiche les notes identifié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 fichier midi.h. Par exemple rmidi/2 (port 2 de l'instruction amidi -l) est sur le port 4.
//===== Programme reception_notes.cc ==========
#include <iostream>
using namespace std;
#include "midi.h"
int main()
{
Midi midi;
//--- ouvre un port midi en entree
int port=0; // numero de port
int res = midi.Initialise_port_midi_in(port);
if(res==0)
cout<<"port est encore ferme."<<endl;
else
cout<<"port ouvert."<<endl;
//--- attente de messages
midi.Attente_Message();
//---analyse des messages recus
for(auto message : midi.L_message)
{
int port_in=(int)message[0]; // numero de port entrant
message.erase(message.begin()); // enleve le 1er octet. Il reste le message midi standard
if(((int)message[0])/16 == 0x9 && ((int)message[2]) > 0 ) // note on avec vel>0
{
int ch=(int)message[0]-0x90;
int key=(int)message[1];
int vel=(int)message[2];
cout<<"Note on recue : port="<<port_in<<" canal="<<ch<<" key="<<key<<" vel="<<vel<<endl;
}
else if(((int)message[0])/16==0x8 || (((int)message[0])/16 == 0x9 && ((int)message[2]) == 0) ) // note off
{
int ch=(int)message[0];
if(ch/16==0x8)
ch-= 0x80;
else if(ch/16==0x9)
ch-= 0x90;
int key=(int)message[1];
int vel=(int)message[2];
cout<<"Note off recue : port="<<port_in<<" canal="<<ch<<" key="<<key<<" vel="<<vel<<endl;
}
}
//--- ferme port midi
midi.Ferme_port_midi_in(port);
}
#==========================================
OBJETS_MIDI_R= reception_notes.o midi.o
midi.o : midi.cc midi.h
$(CC) $(CFLAGS) -c $*.cc -o $@
reception_notes.o : reception_notes.cc
$(CC) $(CFLAGS) -c $*.cc -o $@
reception_notes: $(OBJETS_MIDI_R)
$(CC) -o reception_notes $(OBJETS_MIDI_R) $(LIBR)
#==========================
all : reception_notes
Chapitre 27 Messages MIDI (Musique) sur fichier
Les messages musicaux MIDI peuvent être écrit dans un fichier. Ce format s'appelle “Standard Midi File” S.M.F.
La convention des messages est très similaire aux messages en temps réel. Il y a peu de différences (il y a “Message méta évènement” et plus de “Message temps réel”)
27.1 Structure d'un fichier SMF
Il est constitué de blocs de données appelés “chunk” (=gros morceau). Le premier bloc annonce les suivants.
Conventions sur le codage des nombres:
- CLF “Codage à longueur fixe”. Un nombre entier positif est codé sur octets avec au format big-endian, c'est à dire que le nombre associé estSauf mention du contraire on utilise CLF.
- CLV “Codage à longueur variable”: pour octets on utilise seulement les 7 derniers bits de chaque octet. Le premier bit quand à lui est (et donc octet ) pour les octets , et pour le dernier octet utilisé le premier bit est (et donc octet ). Le nombre associé est (format big-endian)
27.1.1 Premier bloc (header chunk)
Il est composé de
- 4 octets: “MThd” = 0x4D546864. Ces 4 caractères invariables en début de fichier signalent que c'est un fichier MIDI.
- : 4 octets: : longueur invariable en octets de ce bloc, comptée après ces 4 octets.
- : 2 octets:
- : si le fichier contient un seul bloc après celui ci. Il contiendra éventuellement plusieurs canaux (pour avoir polyphonie)
- : (le plus courant) si le fichier contient plusieurs blocs après celui ci. Ils seront joués simultanément. (polyphonie). Les métadonnées sont sur le premier bloc.
- : si le fichier contient plusieurs blocs après celui ci. Ils seront joués les un après les autres dans le temps.
- : 2 octets: nombre de blocs qui vont suivre ce premier bloc.
- : 2 octets: “Delta time” ou “unité de temps”.
- Si le premier bit de est , cad , alors l'unité de temps estoù qui est le tempo cad la durée entre deux pulsation. ex: est “Andante” donnée après dans les méta données (ou pas donnée). En général,
- Si le premier bit de est , cad , alors est codé en “SMPTE compatible units. Midi time code” cadavec est le nombre d'images par secondes (ex: ) et est le nombre de subdivisions d'une image (“frame”)
27.1.2 Bloc de piste (Track chunk)
Il est composé de
- 4 octets: “MTrk” = 0x4D54726B
- : 4 octets: longueur en octets de ce bloc. (comptés après ces 4 octets)
- Une suite d'”évènements midi” (Midi event). Chaque évènement midi est composé de
- : nombre de pas de temps écoulés depuis l'évènement précédant, codé au format CLV.
- : Un message midi (le nombre d'octets dépend du premier quartet):
- Si le premier quartet (4 bits) a le premier bit , cad alors a 2 ou 3 octets.
- Si le premier quartet (4 bits) a le premier bit , cad alors a deux octets, c'est à dire que la dernière action est répétée, meme 1er octet, “running status”. Par exemple pour coder l'accord C-E-G sur le canal 3
- (x00 - x93 - x3C - x40) (x00 - x93 - x40 - x40) (x00 - x93 - x43 - x40) : sans running status
- ou (x00 - x93 - x3C - x40) (x00 - x40 - x40) (x00 - x43 - x40) : avec running status
La fin de bloc est annoncée par le message avec 3 octets: xFF, x2F, x00
27.1.3
Message Midi standard
a 2 ou 3 octets . Le premier octet est décomposé en 2 quartet (4 bits chacuns)où est le numéro de canal. Les octets suivants sont
|
signification et nombres d'octets à suivre
|
|
|
|
Messages standard
|
|
|
x8c
|
Note est éteinte sur un canal
|
note:
|
durée de l'extinction, 0:lent
|
x9c
|
Note est jouée sur canal
|
note
|
intensité. (: éteind)
|
xAc
|
Aftertouch sur canal (vibrato sur une note)
|
note
|
force du vibrato
|
xBc
|
Control Change sur canal (paramétrage d'un canal) cf détails en 27.1.7.
|
type
|
valeur
|
xCc
|
Program Change sur canal (changement d'instrument sur un canal)
|
instrument
|
-
|
xDc
|
Canal pressure sur canal (vibrato sur un canal)
|
vibrato
|
-
|
xEc
|
Pitch Wheel Change (glissando sur un canal) décale de .
|
|
|
27.1.4 Message méta évènement (pour les signaux midi sur fichier et non pas temps réel)
|
signification
|
|
|
xFF
|
Méta-évènement. cf Section 27.1.8
|
type
|
Lenght (+sieurs octets)
|
27.1.5 Message du système (System Exclusive Message (SysEx))
|
signification
|
|
|
xF0
|
System Exclusive Message (SysEx)
|
|
|
xF7
|
|
|
|
Le message se termine par le même octet 0xF7:
- 0xF0 + <data_bytes> 0xF7
- ou: 0xF7 + <data_bytes> 0xF7
27.1.6 Message temps réel (pour les signaux midi temps réel et non sur fichier)
|
signification
|
xF8
|
Timing Clock: Envoyé 24 fois par noire en cas de synchronisation
|
xFA
|
Start: Démarre une séquence. Il est possible souvent d'autoriser un clavier à déclencher le démarrage d'une séquence (typiquement pour un enregistrement).
|
xFB
|
Continue: Reprend une séquence à l'endroit où elle avait été arrêtée.
|
xFC
|
Stop: Arrête la séquence en cours.
|
xFE
|
En cas de premier envoi, ce message doit être répété avec un délai maximum de 0,3 seconde, sinon la connexion sera réputée coupée, et tous les voix éteintes
|
xFF
|
Reset Réinitialisation
|
27.1.7
Control Change: messages
Valeurs possible du type
x00 Bank Select: sélection d'une banque de sons
x01 Modulation Wheel: roulette de modulation (à 0 si réinitialisation)
x02 Breath Controller: contrôle par le souffle
x04 Foot Controller: contrôle au pied
x05 Portamento Time: rapidité du portamento
x06 Data Entry MSB: octet fort de donnée supplémentaire
x07 Channel Volume: volume principal d'un canal
x08 Balance
x0A Pan: panoramique, 64 est le milieu
x0B Expression Controller: contrôle de l'expression (127 si réinitialisation)
x0C Effect Control 1: effet 1
x0D Effect Control 2: effet 2
x10 à x13 General Purpose 1-4: contrôleurs tout usage
x20 à x3F octet de poids faible pour les contrôleurs 0-31
x40 Damper Pedal: maintient toutes les notes (0 si réinitialisation)
x41 Portamento On/Off (0 si réinitialisation)
x42 Sostenuto On/Off: sustain pour la note jouée au moment du déclenchement (0 si réinitialisation)
x43 Soft Pedal On/Off: pédale douce (0 si réinitialisation)
x44 Legato Footswitch: contrôle de legato au pied
x45 Hold 2: autre sustain
x46 Sound Controller 1 / Sound Variation: variation du timbre
x47 Sound Controller 2 / Timbre/Harmonic Intens.: contenu harmonique du timbre
x48 Sound Controller 3 / Release Time: temps de release par défaut
x49 Sound Controller 4 / Attack Time: temps d'attaque
x4A Sound Controller 5 / Brightness: brillance
x4B Sound Controller 6 / Decay Time: temps d'extinction du son
x4C Sound Controller 7 / Vibrato Rate: fréquence de vibrato
x4D Sound Controller 8 / Vibrato Depth: profondeur du vibrato
x4E Sound Controller 9 / Vibrato Delay: délai avant le vibrato
x50 à x53 General Purpose Controller 5-8
x54 Portamento Control
x58 High Resolution Velocity Prefix
x5B Effects 1 Depth / Reverb Send Level: intensité de réverbération
x5C Effects 2 Depth / formerly Tremolo Depth: intensité de trémolo
x5D Effects 3 Depth / Chorus Send Level: intensité de chorus
x5E Effects 4 Depth / formerly Celeste Detune Depth: profondeur du désaccord
x5F Effects 5 Depth / formerly Phaser Depth: intensité de phaser
x60 Data Increment (Data Entry +1): incrémentation de donnée (pas d'octet de valeur)
x61 Data Increment (Data Entry -1): décrémentation de donnée (pas d'octet de valeur)
x62 NRPN / Non-Registered Parameter Number - LSB: octet de poids faible de numéro de paramètre non enregistré
x63 NRPN / Non-Registered Parameter Number - MSB: octet de poids fort de numéro de paramètre non enregistré
x64 RPN / Registered Parameter Number - LSB: octet de poids faible de numéro de paramètre enregistré
x65 RPN / Registered Parameter Number - MSB: octet de poids fort de numéro de paramètre enregistré
x78 - x00 All Sound Off: éteint tous les sons, même les fins d'enveloppe
x79 - x00 Reset All Controllers: réinitialise tous les contrôles
x7A - xnn Local Control On/Off: désactive (x00) ou réactive (x7F) la production du son par le clavier maître
x7B - x00 All Notes Off: éteint toutes les notes (mais pas les enveloppes), comme le font les quatre suivant
x7C - x00 Omni Mode Off - recommandé explanations
x7D - x00 Omni Mode On: envoie les événements sur tous les canaux
x7E - xnn Mono Mode On: ne permet qu'une note à la fois sur un canal, ce qui permet de mieux simuler la production de son d'un instrument monophonique (portamento); xnn est le nombre de canaux???
x7F - x00 Poly Mode On: permet plusieurs notes simultanées sur un même canal
27.1.8
type pour les Meta-Evenement
Un meta-evenement contient
- 1 octet: “meta-type event”: (cf cidessous)
- 1 octet : length of meta event data ou en codage CLV?
- the actual event data.
Par exemple: 3 octets: xFF, x2F, x00 en fin de bloc.
type (en hexa)
|
signification
|
00
|
Sequence Number
|
01
|
Text Event
|
02
|
Copyright Notice
|
03
|
Sequence Name
|
04
|
Instrument Name
|
05
|
Lyric/Display
|
06
|
Marker text
|
07
|
Cue point
|
08
|
Program Name
|
09
|
Peripheric Name
|
20
|
MIDI channel prefix assignment
|
|
End of track (cf + haut)
|
51
|
Tempo Setting
|
84 (dec)
|
SMPTE Offset
|
88(dec)
|
Time Signature
|
89 (dec)
|
Key Signature
|
127 (dec)
|
Sequencer Specific Event
|
27.1.9 Systeme exclusif Messages (Sysex)
Ces identifiants servent lors de messages spécifiques au matériel (SysEx, DLS and XMF). Les membres historiques du MMA disposent d'un identifiant d'un octet; les plus récents consistent en deux octets préfixés de l'octet nul.
x00 (préfixe) x0E Matthews Research x24 Hohner x46 Kamiya Studio
x01 Sequential Circuits x10 Oberheim x25 Crumar x47 Akai
x02 Big Briar x11 PAIA x26 Solton x48 Victor
x03 Octave / Plateau x12 Simmons x27 Jellinghaus Ms x4B Fujitsu
x04 Moog x13 DigiDesign x28 CTS x4C Sony
x05 Passport Designs x14 Fairlight x29 PPG x4E Teac
x06 Lexicon x15 JL Cooper x2F Elka x50 Matsushita
x07 Kurzweil x16 Lowery x36 Cheetah x51 Fostex
x08 Fender x17 Lin x3E Waldorf x52 Zoom
x09 Gulbransen x18 Emu x40 Kawai x54 Matsushita
x0A Delta Labs x1B Peavey x41 Roland x55 Suzuki
x0B Sound Comp. x20 Bon Tempi x42 Korg x56 Fuji Sound
x0C General Electro x21 S.I.E.L. x43 Yamaha x57 Acoustic Technical Lab.
x0D Techmar x23 SyntheAxe x44 Casio
Chapitre 28 JUCE
Chapitre 29 Le traitement d'images et de vidéos avec la librairie OpenCV
OpenCV est spécialisée pour le traitement d'image et de vidéos (CV signifie “Computer-Vision”). Un
Tutoriel. Livre conseillé: “OpenCV by example” de P. Joshi, D.M. Escriva et V. Godoy.
29.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-config --cflags --libs opencv`
g++ main.cpp -o main -g -std=c++11 `pkg-config --cflags --libs opencv`
29.2 Lecture et ecriture de fichiers images
Les commentaires expliquent les fonctions. Il faut avoir le fichier
Image.jpg dans le répertoire du programme.
#include <iostream>
using namespace std;
#include <string>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
//=========================
int main()
{
//..... Lecture des images -> img
Mat img= imread("Image.jpg");
//...... Ecriture d'une image: img -> fichier Image2.jpg
imwrite("Image2.jpg", img);
//..... Lecture d'un pixel
int x=img.cols/2; // selectionne point (x,y) au centre de l'image
int y=img.rows/2;
Vec3b p= img.at<Vec3b>(x,y);
cout << "La valeur du pixel en x="<<x<<", y="<<y<<" est (B,G,R): (" << (int)p[0] << "," <<(int)p[1] << "," << (int)p[2] << ")" << endl;
//..... dessin de l'image
imshow("Image", img);
//..... Attend qu'une touche soit appuyee.
int c = waitKey(0); // rem: montre le dessin. Renvoit le code de la touche. On peut mettre une duree t en ms. ex: 2000
}
Resultat:
- Affiche deux images: une en couleur et l'autre en noir et blanc.
- Pixel value (B,G,R): (3,7,12)
29.2.0.1 Fonctions utiles sur les images
Copie d'une image:
- La commande Mat image2 = image1; ne fait pas une copie des images mais seulement de l'adresse (c'est à dire que image1 et image2 vont désigner la même image). Pour dupliquer l'image il faut faire image1.copyTo(image2);
Recadrage d'une image:
- resize(frame, frame, Size(), scalingFactor, scalingFactor, INTER_AREA);
- Mat img= imread("Image.jpg", 0); // 0: convertit en gris
29.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 fichier image
M2.png à télécharger dans le même répertoire.
-
#include <iostream>
using namespace std;
#include <string>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
//-------------------------------
Mat img, image, roi;
int etat_select = 0;// 0: selection pas faite, 1: selection en cours, 2: selection faite
Rect selection; // taille de la selection
Point origin; // point de depart de la selection
//---- fonction appelee si la souris est actionnee
static void onMouse( int event, int x, int y, int, void* )
{
// cout<<"event="<<event<<" x="<<x<<" y="<<y<<endl;
//--- si action du clic souris
switch( event )
{
case 1: // clic gauche down
if(etat_select==0)
{
origin = Point(x,y);
selection = Rect(x,y,0,0);
etat_select = 1;
}
break;
case 4: // clic gauche up
if( etat_select==1 && selection.width > 0 && selection.height > 0 )
{
etat_select =2; // selection finie
bitwise_not(roi, roi); // inversion de roi (dans image)
imshow("Image", roi);
}
break;
case 2: //clic droit down
if(etat_select==2)
{
etat_select = 0;
imshow("Image", img);
}
}
//--- si deplacement souris, traitement de l'image
if( etat_select == 1 )
{
selection.x = MIN(x, origin.x);
selection.y = MIN(y, origin.y);
selection.width = std::abs(x - origin.x);
selection.height = std::abs(y - origin.y);
selection &= Rect(0, 0, img.cols, img.rows); // pour ne pas depasser image totale
if(selection.width > 0 && selection.height > 0 )
{
img.copyTo(image);
roi = Mat(image, selection); // la matrice roi est une selection de image
bitwise_not(roi, roi); // inversion de roi (dans image)
imshow("Image", image); // montre image avec sa selection inversee
}
}
}
//=========================
int main()
{
//..... Lecture des images
img= imread("M2.png");
//..... Affiche l'image a l'ecran
namedWindow( "Image", 1 );
setMouseCallback( "Image", onMouse, 0 ); // associe la fonction de lecture de la souris
imshow("Image", img);
waitKey(0);
}
29.4 Lecture et écriture d'un fichier video ou de la webcam
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
//-------------------------------------
int main()
{
//.... ouverture du fichier video ou webcam
String nom = "video_pendule.mp4"; // fichier video
VideoCapture cap;
cap.open(nom);
//cap.open(0);// ouvre la webcam.
if(!cap.isOpened()) // si erreur d'ouverture
return -1;
//...ouverture d'un fichier video en ecriture
String nom2= "video_pendule2.avi"; // fichier video
Size frameSize(static_cast<int>(cap.get(CV_CAP_PROP_FRAME_WIDTH)), static_cast<int>(cap.get(CV_CAP_PROP_FRAME_HEIGHT)));
VideoWriter out (nom2, CV_FOURCC('P','I','M','1'), 20, frameSize, true); //initialize the VideoWriter object
if ( !out.isOpened() )
return -1;
//.... boucle infinie
while(true)
{
Mat img;
cap >> img; // extrait la prochaine image
imshow("Video", img); // dessin de img dans la fenetre Video
out.write(img); // ecrit l'image dans fichier
if(waitKey(30) >= 0) break; // Montre dessin. Attente de 30 ms et arret si touche appuyee
}
cap.release(); //libere la memoire allouee
}
29.5 Détection d'objet par leur couleur
Voici un exemple de programme qui lit une image et selectionne les objets bleus. Pour afficher les images intermediares, il faut décommenter les lignes correspondantes.
- Voici une documentation sur les formats de couleurs. On utilise le format HSV (voir aussi HSV sur wikipedia) qui est proche de la perception humaine: est la teinte de la couleur, par exemple est une palette de bleus. est la saturation entre (blanc) et (couleur vive). est la luminosité entre (noir) et
- Sauver l'image “objet_bleu.jpg” ci-dessous dans le répertoire du programme.
- Résultat du programme:
#include <iostream>
using namespace std;
#include <string>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
//#include "opencv2/core/utility.hpp"
#include "opencv2/imgproc.hpp"
//#include "opencv2/highgui.hpp"
using namespace cv;
//=========================
int main()
{
//------------------
Mat img= imread("objet_bleu.jpg");
//imshow("img", img);
//------ convertit format RGB -> format HSV
Mat img_hsv;
cvtColor(img, img_hsv, COLOR_BGR2HSV); // img -> img_hsv
//imshow("img_hsv", img_hsv);
//-------- seuillage pour extraire le bleu -> masque
Scalar inf = Scalar(60,100,100); // limite inf du bleu (code HSV entre 0 et 255)
Scalar sup = Scalar(180,255,255); // limite sup du bleu
Mat mask;
inRange(img_hsv, inf, sup, mask); // img_hsv -> matrice mask : contient 0 (pas bleu) ou 1 (bleu)
//imshow("mask", mask);
// ----- on applique le masque sur l'image
Mat img2;
bitwise_and(img, img, img2, mask); // img, mask -> img2
//imshow("img2", img2);
//--------On lisse l'image
Mat img3;
medianBlur(img2, img3, 5); // lisse l'image sur taille 5. img2 -> img3
imshow("img3", img3);
//..... Attend qu'une touche soit appuyee.
waitKey(0);
}
29.6 Détection et suivit d'objet de couleur avec l'algorithme CamShift
Le code ci-dessous est tiré du site web de opencv.
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <ctype.h>
using namespace cv;
using namespace std;
//-------------------------------
Mat image;
bool backprojMode = false; // true: si on montre l'image seuillee
bool showHist = false; // true: on montre l'histogramme
bool selectObject = false; // true: si on est en train de selectionner
int trackObject = 0; //0: pas de selection faite. -1: selection faite mais à traiter. 1: selection faite et traitee.
Rect selection; // taille de la selection
Point origin; // point de depart de la selection
//---- Si l'utilisateur selection un rectangle -> selection et le flag trackObject =1.
static void onMouse( int event, int x, int y, int, void* )
{
// cout<<"event="<<event<<" x="<<x<<" y="<<y<<endl;
if( selectObject )
{
selection.x = MIN(x, origin.x);
selection.y = MIN(y, origin.y);
selection.width = std::abs(x - origin.x);
selection.height = std::abs(y - origin.y);
selection &= Rect(0, 0, image.cols, image.rows); // pour ne pas depasser image totale
}
switch( event )
{
cout<<"event="<<event<<endl;
case CV_EVENT_LBUTTONDOWN: // coin de depart
origin = Point(x,y);
selection = Rect(x,y,0,0);
selectObject = true;
break;
case CV_EVENT_LBUTTONUP:
selectObject = false;
if( selection.width > 0 && selection.height > 0 )
trackObject = -1;
break;
}
}
//-------------------------------
static void help()
{
cout << "\nThis is a demo that shows mean-shift based tracking\n"
"You select a color objects such as your face and it tracks it.\n"
"Usage: \n"
" ./camshiftdemo \n";
cout << "\n\nHot keys: \n"
"\tESC - quit the program\n"
"\tc - stop the tracking\n"
"\tb - switch to/from backprojection view\n"
"\th - show/hide object histogram\n"
"\tp - pause video\n"
"To initialize tracking, select the object with mouse\n";
}
///-------------------------------
int main( int argc, const char** argv )
{
help();
VideoCapture cap;
Rect trackWindow;
int hsize = 16; // pour histogramme
float hranges[] = {0,180};
const float* phranges = hranges;
String videoFile= "pendule_jaune.mp4"; // fichier video
// cap.open(videoFile); // ouvre le fichier video
cap.open(0); // ouvre camera
if( !cap.isOpened() )
{
cout << "***Could not initialize capturing...***\n";
return -1;
}
//... initialise les fenetres
namedWindow( "Histogram", 0 );
namedWindow( "CamShift Demo", 0 );
setMouseCallback( "CamShift Demo", onMouse, 0 ); // associe la fonction de lecture de la souris
//.... pose les sliders
int vmin = 10, vmax = 256, smin = 30;
createTrackbar( "Vmin", "CamShift Demo", &vmin, 256, 0 );
createTrackbar( "Vmax", "CamShift Demo", &vmax, 256, 0 );
createTrackbar( "Smin", "CamShift Demo", &smin, 256, 0 );
Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;
bool paused = false;
//------ boucle infinie ----------
for(;;)
{
if( !paused )
{
cap >> frame;
if( frame.empty() )
break;
}
frame.copyTo(image); // -> image
if( !paused )
{
cvtColor(image, hsv, COLOR_BGR2HSV); // convertit -> hsv
if( trackObject )
{
int _vmin = vmin, _vmax = vmax;
// seuillage -> mask (fenetre de 0 et 1)
inRange(hsv, Scalar(0, smin, MIN(_vmin,_vmax)), Scalar(180, 256, MAX(_vmin, _vmax)), mask);
//imshow( "mask", mask);
// Mix the specified channels
int ch[] = {0, 0};
hue.create(hsv.size(), hsv.depth()); // -> hue: nouvelle fenetre vide de meme taille et type
mixChannels(&hsv, 1, &hue, 1, ch, 1); // -> copie hsv dans hue
// on traite la selection -> histogramme
if( trackObject < 0 )
{
cout<<"ici"<<endl;
// Create images based on selected regions of interest (roi)
Mat roi(hue, selection), maskroi(mask, selection);
// imshow( "hue", hue);
//imshow( "mask", mask);
//waitKey(0);
// Compute the histogram and normalize it -> tableau hist
calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
normalize(hist, hist, 0, 255, CV_MINMAX);
trackWindow = selection;
trackObject = 1;
histimg = Scalar::all(0);
int binW = histimg.cols / hsize;
Mat buf(1, hsize, CV_8UC3);
for( int i = 0; i < hsize; i++ )
buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i*180./hsize), 255, 255);
cvtColor(buf, buf, CV_HSV2BGR);
// dessin des rectangles de l'histogramme sur la fenetre histimg
for( int i = 0; i < hsize; i++ )
{
int val = saturate_cast<int>(hist.at<float>(i)*histimg.rows/255);
rectangle( histimg, Point(i*binW,histimg.rows), Point((i+1)*binW,histimg.rows - val), Scalar(buf.at<Vec3b>(i)), -1, 8 );
}
}
// Compute the histogram backprojection
calcBackProject(&hue, 1, 0, hist, backproj, &phranges); // -> backproj
backproj &= mask; // ameliore backproj
//... trackwindow, backproj -> trackBox
RotatedRect trackBox = CamShift(backproj, trackWindow, TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
// Check if the area of trackingRect is too small
if( trackWindow.area() <= 1 )
{
// Use an offset value to make sure the trackingRect has a minimum size
int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
trackWindow = Rect(trackWindow.x - r, trackWindow.y - r,
trackWindow.x + r, trackWindow.y + r) &
Rect(0, 0, cols, rows);
}
if( backprojMode )
cvtColor( backproj, image, COLOR_GRAY2BGR );
// draw rotated rectangle trackBox
Point2f rect_points[4];
trackBox.points( rect_points );
RNG rng(12345);
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
for( int j = 0; j < 4; j++ )
line( image, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );
// Draw the ellipse on top of the image
ellipse( image, trackBox, Scalar(0,0,255), 3, CV_AA );
}
}
else if( trackObject < 0 )
paused = false;
// Apply the 'negative' effect on the selected region of interest
if( selectObject && selection.width > 0 && selection.height > 0 )
{
Mat roi(image, selection);
bitwise_not(roi, roi);
}
imshow( "CamShift Demo", image );
imshow( "Histogram", histimg );
char c = (char)waitKey(10);
if( c == 27 ) // esc.
break;
switch(c)
{
case 'b':
backprojMode = !backprojMode;
break;
case 'c':
trackObject = 0;
histimg = Scalar::all(0);
break;
case 'h':
showHist = !showHist;
if( !showHist )
destroyWindow( "Histogram" );
else
namedWindow( "Histogram", 1 );
break;
case 'p':
paused = !paused;
break;
default:
;
}
}
return 0;
}
Chapitre 30 AI, ML, Deep Learning
Références:
30.1 Coral accelerator
Chapter 31 Autres librairies conseillées (?)
31.1 algèbre linéaire
31.2 Audio
31.3 MIDI
31.4 Images, vidéos, graphisme et boites de dialogues
- GSL. Voir gslsanbox.
- SDL pour le graphisme de base (avec OpenGL) et le son audio. Librairie bien adapté pour les jeux.
- 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.
31.5 Mécanique des fluides, EDP non linéaires
Chapitre 32 Emscripten: conversion d'un programme C++ en programme Java Script
- Le langage javascript est reconnu par tous les navigateurs internet et donc les programmes écrits en javascript peuvent tourner sur toutes les machines (ou smartphone ou tablettes). Javascript fait partie des trois langages de base pour la programmation des pages web:
- HTML qui définit le contenu des pages web. Tutorial. Reference. Voici un site qui permet de valider votre code html, ou trouver les erreurs.
- CSS qui spécifie le style des pages web. Tutorial. Reference.
- JavaScript qui permet de programmer le comportement des pages web. Tutorial. Reference.
- Le logiciel emscripten permet de convertir automatiquement du code C++ en code javascript. Cela évite de convertir soi même le code à la main, ou d'écrire en javascript (si on ne connait pas ce langage). De plus le code convertit et compilé peut être plus rapide que du code javascript natif: il produit du code appelé bytecode ou webassembly qui est ensuite compilé (i.e. convertit) par le navigateur en langage machine plus rapide.
- Il y a une alternative: Cheerp. Comparaison entre cheerp et emscripten
Dans cet Section, on explique comment faire cela avec des exemples très simples. Voici des exemples sophistiqués:
Box2d ou
Bullets
Documentations:
32.1 Installation
On vérifie que l'installation est correcte par ./emcc -v
32.2 Conversion C++ -> Javascript
Exemple de base:
Copier le code suivant dans le fichier test.cc
#include <iostream>
using namespace std;
double somme(double a,double b)
{
return a+b;
}
int main()
{
double a=2, b=3.1;
cout<<" a="<<a<<", b="<<b<<", a+b="<<a+b<<endl;
}
Compilation:
emcc test.cc -std=c++11 -O2 -o test.js
Cela produit l'executable javascript test.js
Execution dans un terminal:
node test.js
Cela affiche:
a=2, b=3.1, a+b=5.1
Pour créer un fichier html:
emcc test.cc -std=c++11 -O2 -o test.html
cela crée un fichier test.html et le script test.js à part, que l'on execute par:
firefox test.html
32.2.1 Autres exemples
Voir /emsdk_portable/emscripten/master/tests/
32.3 Pour connecter un code C++ et une interface html
On décrit ici l'utilisation de
Cwrap. Autre possibilité serait une utilisation de
WebIDL (voici un
exemple).
32.3.1
Exemple de base
Résultat:
Code C++:
Copier le code suivant dans le fichier test.cc
#include <math.h>
extern "C"
{
//-------------
int f1(int x)
{
return x+2;
}
//-------------
char* f2(char*s)
{
char *u;
u = new char[3];
u[0]=s[0];
u[1]=s[1];
u[2]='\0'; // fin de chaine
return u;
}
}
Compilation:
dans un terminal écrire:
emcc test.cc -std=c++11 -O2 -o test.js -s EXPORTED_FUNCTIONS="['_f1','_f2']"
Cela produit l'executable javascript test.js
Code html:
Ecrire le code suivant dans un fichier code.html
<!DOCTYPE html>
<html>
<body>
<p>Entrer nombre x :</p>
<input id="n">
<button type="button" onclick="F()">Submit</button>
<h3>resultat f1(x)=x+2:</h3>
<p id="zone1"></p>
<h3>resultat f2(x): 2 premiers caracteres de x sont:</h3>
<p id="zone2"></p>
<script src="test.js"></script>
<script>
function F()
{
var x = document.getElementById("n").value; // recupere entree (string)
//........
f1 = Module.cwrap('f1', 'number', ['number']); // declaration de la fonction qui est definie dans test.js
var y1 = f1(x);
document.getElementById("zone1").innerHTML = y1; // envoie resultat (string)
//........
f2 = Module.cwrap('f2', 'string', ['string']);
var y2 = f2(x);
document.getElementById("zone2").innerHTML = y2; // envoie resultat (string)
}
</script>
</body>
</html>
Execution:
avec la commande firefox code.html
Attention il faut que le script test.js soit présent dans le même répertoire.
Partage du programme:
Si vous posez les fichiers code.html et test.js dans le même répertoire sur un serveur web, alors un utilisateur extérieur qui charge le fichier code.html pourra exécuter votre programme.
Cela marchera depuis toute plateforme (ordinateur sous linux, windows, mac ou même iphone, android etc).
Par exemple le programme précédent est utilisable ici:
code.html.
- Dans le fichier code.html, on écrit f1 = Module.cwrap('f1', 'number', ['number','number']); pour une fonction avec deux entrée et une sortie. En sortie: on met null si il n'y a pas de variable. array si on renvoit un tableau d'octets.
- Pour échanger des tableaux, pointeurs et pointeurs de pointeurs, voir ici ou ci-dessous
- Il est important de savoir (et parfois utile) que si il y a des variables globales (ex: des tableaux etc) dans le code c++, alors leur contenu est conservé à l'appel suivant.
- A la compilation si il y a beaucoup de fonctions à exporter on peut écrire:
- -s EXPORTED_FUNCTIONS=@file.txt and the contents of that file will be used.
32.3.2 Echange de tableaux
Résultat:
Code C++:
Copier le code suivant dans le fichier test.cc
#include <emscripten.h>
//=========================
extern "C"
{
int f(float factor, float *arr, int length)
{
for (int i = 0; i < length; i++)
{
arr[i] = factor * arr[i];
}
return 0;
}
}
Compilation:
dans un terminal écrire:
emcc test_array.cc -std=c++11 -O2 -o test_array.js -s EXPORTED_FUNCTIONS="['_f']"
Cela produit l'executable javascript test_array.js
Code html:
Ecrire le code suivant dans un fichier code.html
<!DOCTYPE html>
<html>
<body>
<button type="button" onclick="F()">Submit</button>
<script src="test_array.js"></script>
<script>
//---------------------------
function Tableau_to_Pointeur(data)
{
var nDataBytes = data.length * data.BYTES_PER_ELEMENT; // data byte size
var dataPtr = Module._malloc(nDataBytes); // allocate memory on Emscripten heap, and get pointer
//.......... Copy data to Emscripten heap (directly accessed from Module.HEAPU8)
var dataHeap = new Uint8Array(Module.HEAPU8.buffer, dataPtr, nDataBytes);
dataHeap.set(new Uint8Array(data.buffer));
return dataHeap;
}
//--------------------
function Pointeur_to_Tableau(dataHeap, N)
{
var data2 = new Float32Array(dataHeap.buffer, dataHeap.byteOffset, N);
Module._free(dataHeap.byteOffset); // Free memory
return data2;
}
//--------------------
function F()
{
var data = new Float32Array([1, 2, 3, 4, 5]);
//---- print array
var txt ='';
for (x in data)
{
txt+= data[x]+","+" ";
}
document.getElementById("zone1").innerHTML= txt;
//--- appel fonction c++ avec un tableau
var p = Tableau_to_Pointeur(data);
f = Module.cwrap('f', 'number', ['number', 'number', 'number']); // import fonction c++
f(2, p.byteOffset , data.length); // appel fonction c++
var data2 = Pointeur_to_Tableau(p, data.length);
//---- print array
var txt2 ='';
for (x in data2)
{
txt2 += data2[x]+","+" ";
}
document.getElementById("zone2").innerHTML= txt2;
}
</script>
<h3>tableau avant appel fonction c++</h3>
<p id="zone1"></p>
<h3>tableau apres appel fonction c++</h3>
<p id="zone2"></p>
</body>
</html>
Execution:
avec la commande firefox code_aray.html
Attention il faut que le script test_array.js soit présent dans le même répertoire.
Le programme précédent est utilisable ici: code_array.html.
32.3.3
Exemple avec des échanges plus complexes
Dans l'exemple précédent, l'interface html et le programme c++ échangent seulement une variable de type int ou une chaine de caractère.
Pour échanger des variables diverses (provenant de “forms” et qui en retour peuvent modifier le contenu de la page html), on pourra tout simplement passer l'information codée dans une unique chaine de caractères.
32.3.3.1 Exemple
Code C++: (fichier test.cc)
#include <math.h>
#include <sstream>
#include <string>
#include <iostream>
using namespace std;
#include <vector>
#include <string.h>
//=======================================
// Decompose la chaine "| .. | ..|" en sous chaines des caractères entre les '|' =delim
vector<string> Decompose(string ligne,char delim)
{
vector<string> instructions;
while(ligne.find(delim)!=string::npos) // il reste une instruction
{
auto pos= ligne.find(delim);
string ligne2=ligne.substr(0,pos);
//.................
instructions.push_back(ligne2);
ligne=ligne.substr(pos+1);
// cout<<" I="<<ligne2<<flush;
}
if(ligne.size()>0)
{
// cout<<" I="<<ligne<<flush;
instructions.push_back(ligne);
}
return instructions;
}
//=========================
extern "C"
{
const char* f(char*s)
{
//--- parse la chaine en entree:
vector<string> vs=Decompose(string(s),'%');
//--- lecture des donnees
double x = stod(vs[0]);
string m=vs[1];
//--- traitement
double y=x+2;
string m2="Salut "+m;
//----fabrique la chaine de sortie
string str_out;
str_out = to_string(y) + “%” + m2; // on choisit le signe % pour separer les données
char * s_out = new char [str_out.length()+1];
strcpy(s_out, str_out.c_str());
return s_out;
}
}
//===========================
int main()
{
//... test ...
char s[]="3.14%fred faure";
const char*u = f(s);
}
Compilation:
emcc test.cc -std=c++11 -O2 -o test.js -s EXPORTED_FUNCTIONS="['_f']"
Code html (fichier code.html):
<!DOCTYPE html>
<html>
<body>
<p>Entrer nombre x :</p>
<input type="number" id="x">
<p>Entrer Nom :</p>
<input id="m">
<button type="button" onclick="F()">Submit</button>
<script src="test.js"></script>
<script>
function F()
{
var x = document.getElementById("x").value;
var m = document.getElementById("m").value;
var s = x.toString() + "%" + m.toString() ;
//........
f = Module.cwrap('f', 'string', ['string'])
var u = f(s);
document.getElementById("zone2").innerHTML = "debug";
var tu = u.split("%"); // on choisit le signe % pour separer les données
document.getElementById("zone1").innerHTML = "x+2=" + tu[0];
document.getElementById("zone2").innerHTML = tu[1];
}
</script>
<h3>resultat 1</h3>
<p id="zone1"></p>
<h3>resultat 2</h3>
<p id="zone2"></p>
</body>
</html>
Execution:
firefox code.html
Résultat:
Pour tester le code C++ seulement:
Cela utilise la fonction main() de test.cc.
Compilation:
g++ test.cc -std=c++11 -O2 -o test
Execution:
./test
32.4 Echange de variables js<->c++. Appeler des fonctions js depuis c++ et des fonctions c++ depuis js
Code c++:
// -*- mode:C++ ; compile-command: "emcc test.cc -std=c++11 -O2 -o test.js -s EXPORTED_FUNCTIONS="['_f']"" -*-
#include <emscripten.h>
//=========================
extern "C"
{
void f()
{
//.. on passe du code js dans une chaine de caractere
string code =" var y=3; document.getElementById(\"zone1\").innerHTML = y;";
emscripten_run_script(code.c_str());
// on passe du code js dans (..)
EM_ASM(
alert('hello world!');
);
// transfert de variables c++ -> js. Elles sont recuperees par $0,$1 etc
double x0=1.2, x1=4.3;
EM_ASM_(
var x0= $0; var x1 = $1; document.getElementById("zone2").innerHTML = "x0 x1 = " + x0 + x1;
, x0, x1);
// transfert de variables INT js -> c++
int x = EM_ASM_INT({
var x=3;
return x;
}, 0);
//.. affiche le contenu
string s = "document.getElementById(\"zone3\").innerHTML = \"" + to_string(x) + "\";";
emscripten_run_script(s.c_str());
// transfert de variables DOUBLE js -> c++
double x = EM_ASM_DOUBLE({
var x=3.14;
return x;
}, 0);
//.. affiche le contenu
string s = "document.getElementById(\"zone4\").innerHTML = \"" + to_string(x) + "\";";
emscripten_run_script(s.c_str());
}
}
Code html:
<!DOCTYPE html>
<html>
<body>
<button type="button" onclick="F()">Submit</button>
<script src="test.js"></script>
<script>
function F()
{
f = Module.cwrap('f', 'string', ['string'])
f();
}
</script>
<h3>resultat 1</h3>
<p id="zone1"></p>
<h3>resultat 2</h3>
<p id="zone2"></p>
<p id="zone3"></p>
<p id="zone4"></p>
</body>
</html>
32.5 Autres remarques
- Le préprocesseur reconnait __EMSCRIPTEN__ qui indique que emcc est le compilateur.
- On peut utiliser des fichiers locaux en c++.
- Une variable déclarée comme:
var x=3;
est locale. Par contre:
x=3;
est globale.
Pour pouvoir ré-utiliser des variables dans du code généré par emscripten_run_script(code.c_str()); il est utile qu'elles soient globales.
32.6 Utilisation de fichiers
Supposons que dans le répertoire courant il y a un fichier “file.txt” contenant “abcd”.
Code c++:
// -*- mode:C++ ; compile-command: "emcc test.cc -std=c++11 -O2 -o test.js -s EXPORTED_FUNCTIONS="['_f']" --preload-file file.txt " -*-
#include <fstream>
using namespace std;
#include <emscripten.h>
//=========================
extern "C"
{
void f()
{
//... lecture depuis fichier
ifstream g("file.txt");
string res;
g>>res;
//.. affiche le contenu
string s = "document.getElementById(\"zone1\").innerHTML = \"" + res + "\";";
emscripten_run_script(s.c_str());
}
}
Commande de compilation
emcc test.cc -std=c++11 -O2 -o test.js -s EXPORTED_FUNCTIONS="['_f']" --preload-file file.txt
Code html:
<!DOCTYPE html>
<html>
<body>
<button type="button" onclick="F()">Submit</button>
<script src="test.js"></script>
<script>
function F()
{
f = Module.cwrap('f')
f();
}
</script>
<p id="zone1"></p>
</body>
</html>
32.7 Utilisation de librairies ?
Tu compiles la librairie avec emcc -c sur les sources puis emar sur les objets pour creer le fichier libarmadillo.a, et tu compiles ton programme en mettant les include des headers de armadillo et en linkant avec -larmadillo
32.8 Utilisation de html5.h
32.8.1 Animation and Timing
Remark: the call of functions are done in the same thread as the main function, i.e. they are blocking.
32.8.1.1 Appel périodique d'une fonction (SetInterval() de Javascript)
// -*- mode:C++ ; compile-command: "emcc main.cc -std=c++11 -O2 -o index.html" -*-
/*
Execution:
----------
python -m SimpleHTTPServer 8000
google-chrome http://localhost:8000/index.html
*/
#include <iostream>
using namespace std;
#include <emscripten/html5.h>
//-------------------
void Loop(void * data)
{
cout<<"Call of Loop()"<<endl;
}
//-------------------
int main()
{
void *userData;
long id = emscripten_set_interval(Loop, 1000, userData);
}
32.9 Utilisation de Threads
Cela est possible avec le navigateur Chrome.
Voir cette
page qui explique et contient un exemple.
32.10 Des classes de widgets particulières
Dans cette section, on présente des classes c++ de widget particulières qui peuvent être utiles.
32.10.1 Fichiers sources
Chapitre 33 Makef: Création automatique de Makefile et d'interface graphique utilisateur (GUI)
Dans ce chapitre, je présente un logiciel que j'ai développé au départ pour usage personnel, qui est très utile lorsque on développe un projet, et permet de gagner du temps.
Utilité:
A partir d'un code C++ il permet au choix,
- Soit de compiler le code avec g++ et d'obtenir un exécutable. Il écrit automatiquement:
- Un fichier Makefile pour un projet c++
- Du code c++ correspondant à des fenêtres de commandes GUI, utilisant la librairie root.
- un fichier archive qui contient le projet, afin de le partager.
- Soit de compiler le code avec emscripten et d'obtenir un code en wasm qui tourne sur un navigateur.
- Du code Javascript correspondant à des fenêtres de commandes GUI, en Javascript.
- un fichier archive qui contient le projet, afin de le partager.
33.1 Mode d'emploi
en partant d'un fichier "nomfichier.cc" qui contient le main().
Pour l'utiliser, il faut
- écrire le fichier makef.config
- lancer : makef repertoire nomfichier.cc makef.config <nom_executable>
- ensuite pour compiler (lancer le Makefile), il
il faut faire :
make clean (pour detruire les fichiers .o precedants)
make all
voir par exemple le script "compp"
33.1.0.1 Fonctionnement final des commandes
- Une fenetre de commande GUI est lancée dans un autre thread (process) "com" et utilise root. Cette fenetre de commande contient tous les widgets des variables (demandees) des classes que utilise le projet main().
- si besoin, on a acces aux fenetres de commandes par le pointeur Com *p_com;
- Communication: écrire:
class Com;
extern Com *p_com; // pointeur sur l'objet (declare dans .cc)
extern mutex mtx;
- Si un widget est activé (changé), le process "com" change les variables du main avec une protection prealable de type mutex.
- depuis le process "main", on appelle explicitement p_com->Met_a_jour() pour que l'affichage de "com" corresponde aux variables de "main".
- optionnellement, le process "com" appelle Met_a_jour() de facon periodique (tous les 0.1 sec), pour eviter cet appel explicite.
- Dans le programme "main", avant/apres d'acceder aux variables partagees, il faut mettre/ enlever le mutex mtx: mtx.lock(); mtx.unlock();
33.1.0.2 sortie du programme makef
fichiers c++: com.cc, com.h dans le meme repertoire que fichier.h qui sont des classes c++ d'un panneau de commandes
33.1.1 Instructions pour placer les widgets
La fenetre de commande peut contenir les zones suivantes:
- un menu,
- une zone commune: ZC
- des zones de tabs: ZT1, ZT2, ...
Les widgets sont placés à la suite horizontalement dans la zone demandee.
- Retour à la ligne: On peut revenir à la ligne si on commence par l'instruction "nl":
ex:
double x1=4; // make_gui = nl N(ZT("tab1"), "x1=")
- Texte d'aide: On peut rajouter un texte d'aide (qui sera un “Tip”). Attention “help” est un mot réservé!!:
ex:
double x1=4; // make_gui = N(ZT("tab1"), "x1=") help = “texte d'aide”
- N: Numerique entre/sortie pour int, double
ex:
int a; // make_gui = N(ZC,"a=")
double x1=4; // make_gui = N(ZT("tab1"), "x1=")
- T : Texte entre/sortie pour string
ex:
string text = "hello" ; //make_gui = T(ZT("tab1"))
- HS: Horizontal Slider entre/sortie pour int/double
ex:
double x = 1 ; // make_gui = HS(ZT("tab1"), 0, 5)
- VS: Vertical Slider: entre/sortie pour int/double
- PB: Progress Bar: sortie pour int,double
ex:
double y = 5 ; // make_gui = PB(ZT("tab2"), 0,5)
- C : Check entree/sortie pour int x=0,1
ex:
int opt = 1 ; //make_gui = C(ZT("tab2"), ":opt")
- L : Liste entre/sortie pour int x \in [0,N-1]
ex:
int choix = 2 ; //make_gui = L(ZT("tab2"), {"a","b","c","d"})
- B : Bouton d'appel pour fonction void f()
void f() ; // make_gui = B(ZC,"C1_f")
- M : Menu d'appel pour fonction void f()
void g() ; // make_gui = M("menu2","C1_g")
- projet: - H1 : Histogramme 1D pour pour vector<int>, vector<double>, vec
les valeurs de ces widgets sont en permanence couplées à la variable c++.
Pour cela il y a un pointeur pour chaque classe.
ex: pointeur p_C1 pour la classe C1
dans la fonction main(), il faut rajouter (avec la typo):
#includ "com.h"
Com *com= new Com(&c1,&c2);
//thread t(Lance_com, &c1, &c2); // lance fenetre de commande dans autre thread. Donne pointeurs sur objets
On met une typo car cela ne doit pas empecher le fonctionnement de fichier.h sans les commandes
(on doit pouvoir desactiver en option la compilation de com.cc, com.h)
et les fichiers existants ne doivent pas etre modifiés.
Chapitre 34 A propos des licences
Le témoignage d'un programmeur:
Avec la licence tu donnes legalement le droit aux gens de utiliser le soft; sinon, ils ont juste le droit de le télécharger. Donc il faut toujours une licence.
Elle doit être dans l'entête de chaque fichier. Si le texte de la license est trop long, dans l'entête du fichier on met juste un paragraphe type indiquant comment obtenir la license; alors celui-ci est mis à la racine dans un fichier COPYING ou LICENSE.
Fais bien attention à ne rien altérer (meme pas les espace et les allés à la ligne), sinon on ne peut plus utiliser des outils (grep et diff) pour savoir quelle est la license; et surtout on se demandera pourquoi le texte a été altéré.
Pour que le soft soit libre, en pratique il faut trouver un texte de license existant, connu et éprouvé. Sinon l'utilisateur ne sait pas exactement quel droits il a (au sens juridique) et doit payer un avocat si il veut être sur. Donc en pratique c'est comme si il n'y avait pas de license pour le commun des mortels.
Pour de l'open-source & libre il y a 2 types de licences:
- Celles où l'auteur donne le soft et ne veut rien en retour; un peu comme quand on donne un cadeau: on ne se soucie plas si il finit utilisé vendu ou à la poubelle. Ce sont les licenses ISC, BSD, X, MIT, ... Elles sont courtes (puisqu'elle ne disent pas grand chose) on les retrouve en entête des .c et .h.
- GNU public license (et autres semblables peu connus). Dans ce cas l'auteur demande des choses à l'utilisateur, typiquement de ne pas revendre le soft et publier les modifs qu'il y fait, etc. Le texte est en conséquence assez long et ibitable. Mais des juristes s'y sont déjà penchés et "il est bon".
On trouve aussi des license de grands industriels, typiquement 30 pages de charabia de juriste que personne ne comprend; elles sont probablement piegés.
On trouve aussi des licences que des gens ont écrit en toute naiveté et qui n'ont pas de valeur (ie un bon avocat pourrait leur faire dire tout et son contraire).
Perso, j'attends rien en retour, donc j'utilise la license ISC pour toute publication.
Chapitre 35 Débugger un programme
35.1 Avec Valgrind
Permet de trouver des bugs mémoire:
use valgrind, fix the first invalid memory access it complains about (all subsequent complaints may just be "fallout" from the first error), repeat until no more errors. If you do not understand how the code fingered by the first invalid memory access could be wrong, cut that part of the code down into an MCVE;
Additional tip: if valgrind's first complaint appears to be about a C library function, such as strcpy, this usually means the bug is in the code that called that function. valgrind's complaints include a stack trace, so use that to identify the part of your code that is responsible
before: put -g in the compilation and remove optimisation 03
valgrind --leak-check=yes main >& log.txt
or
valgrind --leak-check=yes --track-origins=yes -s main >& log.txt
or
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s main >& log.txt
Références