Université Grenoble alpes.
Auteur:Frédéric Faure
Version: 14 mars 2020


Didacticiel
Bases de programmation en C++

Table des matières

Chapitre 1 Introduction
Partie I Bases du langage C++
Chapitre 2 Le tout début. Lire le clavier et afficher à l'écran
Chapitre 3 Déclaration et affectation des objets
Chapitre 4 Les instructions de base
Chapitre 5 Les fonctions
Chapitre 6 Les pointeurs
Chapitre 7 Création d'une classe
Chapitre 8 Les fichiers
Chapitre 9 Micro-projets 1
Partie II Compléments sur le langage C++
Chapitre 10 Autres environnements
Chapitre 11 Ecrire et lire des données en binaires sur un fichier (ou un flux)
Chapitre 12 Quelques trucs utiles en C++
Chapitre 13 Classes (suite)
Chapitre 14 Gestion de projets avec fichiers .h, Makefile, Git et Doxygen
Chapitre 15 Interface interactive pour le C++
Partie III Compléments sur les librairies du langage C++
Chapitre 16 Librairie Root: graphisme et autre.. (compléments)
Chapitre 17 Mesure du temps
Chapitre 18 Calcul numérique avec une précision arbitraire
Chapitre 19 Intégrer des équations différentielles ordinaires (EDO) avec la librairie ODEINT
Chapitre 20 Lecture et écriture d'un fichier son (audio) de format WAV
Chapitre 21 Audio avec RtAudio
Chapitre 22 Gestion de la carte son (audio temps réel) avec la librairie sndio
Chapitre 23 Threads: plusieurs programmes en parallèle qui communiquent
Chapitre 24 CGI: communication avec un serveur web
Chapitre 25 Sockets: communications entre process via le réseau
Chapitre 26 Messages MIDI Musical temps réel avec la librairie sndio
Chapitre 27 Messages MIDI (Musique) sur fichier
Chapitre 28 JUCE
Chapitre 29 Le traitement d'images et de vidéos avec la librairie OpenCV
Chapitre 30 AI, ML, Deep Learning
Chapter 31 Autres librairies conseillées (?)
Chapitre 32 Emscripten: conversion d'un programme C++ en programme Java Script
Chapitre 33 Makef: Création automatique de Makefile et d'interface graphique utilisateur (GUI)
Chapitre 34 A propos des licences
Chapitre 35 Débugger un programme

Déroulement des séances en Licence L3 de physique, 2017-2018

Chapitre 1 Introduction

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


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:
  1. écrire le code d'un programme en C++
  2. 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.
  3. exécuter le programme
  4. é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

image: 52_home_faure_enseignement_informatique_c++_cours_c++_cours_1_terminal.png

1.2.1.2 Exercices sur les répertoires

A l'aide des commandes expliquées ci-dessus:
  1. Placez vous dans votre répertoire d'origine: cd
  2. Vérifiez que vous y êtes bien: pwd
  3. Regardez la liste des fichiers par: ls , puis avec: ls -als
  4. Créer un répertoire du nom: essai Vérifier son existence (avec ls).
  5. Aller dans ce répertoire: cd essai Vérifier que vous y êtes (avec pwd).
  6. 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)

1.2.1.4 La documentation sur les commandes unix

1.2.2 Programmer avec l'éditeur emacs et une fenêtre de commandes

  1. 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),
  2. 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”.
  3. 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 !).
  4. Dans emacs ouvrir un nouveau fichier Makefile qui sera vide la première fois. Dans ce fichierCopier/coller le code de compilation suivant:
    all:
    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- .
  5. 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”.
  6. 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.
  7. 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.  
  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.
  2. 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.
  3. 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.
  4. 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:

Partie I Bases du langage C++

Chapitre 2 Le tout début. Lire le clavier et afficher à l'écran

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
Remarque 2.1.2. sur la syntaxe du programme:
  1. La classe int permet justement de stocker des nombres entiers. (Cela vient de integer en anglais).
  2. Le point virgule sépare les instructions.
  3. Les commentaires doivent débuter par //. Le reste de la ligne est alors considéré comme un commentaire. Un commentaire n'a aucune influence sur le programme, il sert en général à expliquer ce que fait le programme pour le rendre compréhensible au lecteur. Une autre possibilité est de commencer le commentaire par /* et de finir quelques lignes après par */.
  4. Le début et la fin d'un bloc d'instructions sont respectivement { et }
  5. "main " veut dire "principal " en anglais. C'est le début du programme principal ou de la fonction principale. Les parenthèses () signifient que le programme principal n'utilise aucun paramètre dans cet exemple. Lorsque vous lancez votre programme, son exécution commence toujours par la première ligne de cette fonction principale qui s'appelle toujours main.
 
Remarque 2.1.3. sur l'affichage:
  1. Pour l'affichage de texte dans un terminal, on utilise l'objet cout. Cet objet cout représente le terminal. Les signes << sont évocateurs: on envoit ce qui suit vers l'écran. On peut enchainer les objets que l'on envoit à l'écran en écrivant sous la forme: cout << A<<B<<C<<D; . Le symbole endl signifie que l'on va à la ligne (end of line = fin de ligne).
  2. Les caractères entre guillements " " sont considérés comme une chaîne de caractères et écrits tels quels à l'écran . Par contre les caractères a, b,c ne sont pas entre “ “. Cela signifie que ce sont des noms d'objets, et qu'il faut afficher le contenu de ces objets (et non pas leur nom).
  3. Ce symbole cout appartient à la bibliothèque iostream qui est chargée grâce à la première ligne #include <iostream>. iostream est un fichier déjà présent sur l'ordinateur. Ce fichier contient des informations sur des commandes C++ d'affichage à l'écran et de saisie de touches appuyées au clavier. Iostream vient de l'anglais: Input (=entrée) , Output (=sortie) , Stream (=flux d'information). En principe pour l'affichage il faudrait écrire std::cout car le symbole cout appartient à “l'espace de nom” appelé std comme “standard”; mais la deuxième ligne using namespace std; signifie que l'on pourra écrire plus simplement cout. (Il peut être utile de ne pas faire cette simplification si le même symbôle cout est utilisé pour autre chose par une autre bibliothèque dans un autre espace de nom comme std2 . On pourra alors les distinguer en écrivant: std::cout et std2::cout).
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
Remarque 2.1.5. Voici la liste des autres caractères spéciaux comme la tabulation “\t”, le retour à la ligne suivante “\n”, le retour au début de ligne “\r”, le caractère “\” lui même par “\\”, etc.

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.
Remarque 2.2.2.  
  1. L'objet cin appartient à la classe istream et représente le clavier. Le signe >> est un opérateur associé à cet objet et qui a pour effet de transférer les données tapées au clavier dans l'objet qui suit (une fois la touche entrée enfoncée).
  2. L'instruction flush à la fin de la phrase d'affichage à pour effet d'afficher la phrase à l'écran sans faire de retour à la ligne. Si on ne met rien (ni flush, ni endl) la phrase n'apparait pas forcément tout de suite à l'écran.

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
± 1 0 ± 308 à 1 0 -9 près
char e;
caractère
bool v;
booléen: vrai (true) ou faux (false)
char * t;
chaîne de caractères
Remarque 3.1.1. Les classes ci-dessus sont les classes de base standard du langage C++ qui existent aussi en langage C. Dans le vocabulaire du C, on dirait plutot type à la place de classe, et variable à la place de objet. Par exemple en écrivant double d; on dirait que d est une variable du type double.

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

  1. i est un int qui vaut 0, x un float qui vaut 1.23, x1 est un double qui vaut 3.4, etc..
  2. Le terme auto signifie que le compilateur devine lui même la classe de l'objet, ici z est un double.
  3. 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 _.
  4. 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.
  5. Un caractère est entre le signe ', comme 'X'.
  6. Un texte (chaîne de caractère) est entre le signe ". Ici l'objet 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.
  7. A la dernière ligne on déclare x=5 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).
  8. 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
Référence: voir setprecision.

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:

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;
}
Remarque 3.1.3. rand() génère un nombre entier aléatoire entre 0 et 32768. Ensuite % signifie modulo, a%b est le reste de la division de a par b. Ainsi rand()%(N+1) est le reste de la division du nombre aléatoire par N+1. C'est donc un entier compris entre 0 et N (inclus). En c++11 il y a une classe random plus élaborée pour générer des nombres aléatoires selon diverses lois.

3.1.6 Fonctions mathématiques

Références
With library Boost:

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
Remarque 3.2.1. L'indice du premier caractère est 0 . (En informatique on numérote souvent à partir de 0 ). Voici plus d'informations et d'exemples sur la classe string.
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

Référence: complex.
 
#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
 
Remarque 3.2.3.  

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
Remarque 3.2.4. Les indices de la liste commencent à 0.
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
 
Remarque 3.2.5. Dans l'exemple ci-dessus, l'objet i qui représente une position sur la liste est de la classe vector<int>::iterator. On utilise ici le terme auto qui détecte cette classe et est plus simple à écrire. Voici plus d'informations et d'exemples sur la classe vector. Pour plus d'informations et d'exemples sur les manipulations de listes (appelées containers) voir algorithms. Reference sur max_element.

3.2.3.1 Poser un objet sans copier:

Voir Move

3.3 Vecteurs et matrices pour l'algèbre linéaire avec la librairie Armadillo

Pour cela on utilise la “ librairie armadillo” dont voici la documentation.
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
Remarque 3.3.1. Vous pourrez de la même façon utiliser d'autres classes selon vos besoins à condition cette fois ci d'inclure le fichier d'entête #include <...> approprié au début du programme et d'associer les librairies correspondantes à la compilation.
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:
Remarque 3.4.1. Root peut s'utiliser aussi en mode interprété C++: pour cela, lancer la commande root dans un terminal, et suivre les instructions...

3.4.1 Commande de compilation

-I/usr/include/root `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:
image: 53_home_faure_enseignement_informatique_c++_cours_c++_cours_1_root_fen1.jpg
Remarque 3.4.2. Le programme précédent est découpé en trois types de blocs:
(A): dans tout programme utilisant Root, ces parties doivent toujours être réécrites. Il y a l'objet theApp de la classe TApplication.
(B): c'est le code qu'il faut pour créer la fenetre graphique (objet c de la Classe TCanvas). Si votre programme fait du graphisme, il faut toujours créer une fenêtre graphique qui contiendra votre dessin.
(C): c'est le code qu'il faut pour créer le cercle (objet e de la Classe TEllipse).
Exercice 3.4.3.  
Remarque 3.4.4. la commande e.Draw() appelle la fonction membre Draw() de la classe TEllipse qui dessine l'objet e. Pour connaitre toutes les opérations possibles que l'on peut effectuer sur l'ellipse, il faut regarder la liste des fonctions membres de cette classe TEllipse. On remarquera dans l'entête, que la classe TEllipse hérite d'autres classes comme la classe TAttLine qui concerne les propriétés des traits. Ainsi la classe TEllipse peut utiliser les fonctions de la classe TAttLine comme la fonction SetLineColor() qui permet de changer la couleur des traits.
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
image: 54_home_faure_enseignement_informatique_c++_cours_c++_cours_1_cercle_motif.jpg

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.
image: 55_home_faure_enseignement_informatique_c++_cours_c++_cours_1_dessin_2D_de_base.png
#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

Référence: string.

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

Avec la classe bitset
 
#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*.
Le faire avec c_str().

3.5.5 Quelques opérations en base 2 (binaire, les bits) et en base 16 (hexadecimale)

Avec la classe bitset
 
#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
Remarque 3.5.1. De même on peut utiliser la base 16 (hexadécimale) avec le préfixe 0x. Par exemple:
a=0xFA;
cout<<a<<endl;
cout<<hex<<a<<endl; // pour afficher en base 16.

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

documentation. Exemple:
#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

documentation. Exemple:
#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

documentation. Exemple:
#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

Pour matrices sparses, voir armadillo, exemples.
#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
Remarque 4.1.1. Remarquer que l'on a déclaré la variable int i dans la boucle for.
Exercice 4.1.2. Ecrire un programme qui affiche les valeurs 0.1 0.2 0.3 .. jusqu'à 10.0.
 
Remarque 4.1.3. Une autre possibilité d'utilisation de la boucle for en C++11 est donné avec l'exemple suivant. Dans ce cas la syntaxe est: for(auto x:L) et signifie “pour chaque objet x dans la liste L fait l'instructions qui suit”.
#include <iostream>
using namespace std;
#include <vector>
 
int main()
{
  vector<int> L={5,7,3,12}; // une liste
  for(auto x:L) // la variable x parcourt la liste L
    cout<<x+1<<",";
}
résultat:
 
6,8,4,13,

Remarque 4.1.4. “Bloc d'instruction”. On a vu qu'un bloc d'instructions (= un ensemble d'instructions) est délimité par des accolades: “{..}”. Par exemple dans une boucle “for”, si vous avez une seule instruction, il n'est pas nécessaire de mettre des accolades. Si vous avez plusieurs instructions il faut mettre des accolades. Exemple:
#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 0 et 2 π par pas de 0.01 radian, faire un programme où l'on voit en animation un cercle de rayon r=1 , qui parcourt le cercle de rayon R=3 dans le sens trigonométrique.
Aide: utiliser la librairie root, Section 3.4, et les formules de trigonométrie x=R cos θ , y=R sin θ . 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 10ms entre chaque dessin.

4.2 Ecrire une condition

On a utilisé ci-dessus la condition i <= 30 qui signifie “ i inférieur ou égal à 30 ”. 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
Remarque 5.1.1.  
  1. La fonction carre renvoit son résultat qui est un objet de la classe double. Pour cela on utilise l'instruction return. Pour appeler cette fonction, on utilise la syntaxe y=carre(x) si bien que le résultat renvoyé est tout de suite stocké dans l'objet y.
  2. Dans le bloc {..} de la fonction carre on a déclaré l'objet j.Par conséquent, cet objet j n'est connu que dans ce bloc et pas ailleurs. On dit que c'est un objet local. On ne peut pas l'utiliser dans la fonction main. De même l'objet x déclaré dans le bloc de la fonction main n'est pas connu ailleurs: On ne peut pas l'utiliser dans la fonction carre.

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
Remarque 5.2.1.  
  1. La déclaration de la fonction carre montre que cette fonction prend un paramètre (l'objet i de la classe int) et ne renvoit rien (à cause du préfixe void qui signifie “vide”). La fonction main appelle la fonction carre et lui passe un parametre qui est l'objet x choisi par l'utilisateur.

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
Remarque 5.4.2. Ici on programme la fonction factorielle; c'est un exercice. Mais cette fonction factorielle est bien sûr déjà programmée comme d'autres fonctions spéciales que l'on trouve dans la librairie “boost”.

Chapitre 6 Les pointeurs

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).
 
 
Schéma qui résume ce que l'on vient d'expliquer:
image: 56_home_faure_enseignement_informatique_c++_cours_c++_cours_1_pointeur1.gif
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;
Remarque 6.1.1. Attention: avant d'effectuer l'opération d'écriture: (*p)=5; il faut être sur que l'adresse du pointeur correspond à un objet existant. Vous avez donc compris que avant d'écrire, il faut prendre le soin de réserver de la place mémoire. On parle d'allocation de la mémoire.
On comprends l'écriture int *p; comme la déclaration que *p est un objet de type int, donc p est un pointeur sur un objet de type int.

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.

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é.
image: 57_home_faure_enseignement_informatique_c++_cours_c++_cours_1_pointeur2.png
Remarque 6.2.1. De façon similaire p=new string[13]; réserve en mémoire une suite de 13 objets de type string
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

  1. 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.};
  2. 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
  3. 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

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.
image: 58_home_faure_enseignement_informatique_c++_cours_c++_cours_1_3_ellipses.png

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:

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:
image: 59_home_faure_enseignement_informatique_c++_cours_c++_cours_1_tellipse1.png
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:
image: 60_home_faure_enseignement_informatique_c++_cours_c++_cours_1_exo_ellipses_couleurs.png

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 R 3 est définit par ses trois composantes ( x,y,z ) . 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

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.

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

Exercice 7.2.1. “Classes” (*)
  1. 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.
  2. 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.
  3. 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;
  4. Appelez cette fonction dans le programme principal et testez le bon fonctionnement.
    Remarque: essayez de comprendre la cause des messages construit et détruit. Il manque un construit. La raison de cela sera expliquée ultérieurement au paragraphe Constructeurs par recopie, et affectations.
  5. 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.

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
Remarque 7.3.1.  

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

reference: ofstream.
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:

  1. 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++.
  2. 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).
  3. 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);
  4. 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.
  5. 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

reference: ifstream.
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:

  1. 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.
  2. 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.
  3. La lecture g>>x depuis le fichier est similaire à la syntaxe de lecture cin>>x depuis le clavier.
  4. A la place de cout<<x<<endl; on peut écrire les instructions:
if(g.good())
cout<<x<<endl;
 
En 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
Remarque 8.3.1. g.seekg(0, g.beg); est équivalent à g.seekg(g.beg); Comme autre option il y a g.seekg(0, g.end); (position fin de fichier + 0) ou g.seekg(0, g.cur); (position actuelle + 0)

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.
  1. Définir clairement l'objectif du programme (surtout s'il y a plusieurs personnes à y collaborer).
  2. 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.
  3. 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).
  4. 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.
  5. 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 x 0 1 . Par récurrence, on définit x t+1 à partir de x t par: x t+1 ={ 1 2 x t  si  x t  est pair 1 2 ( 3 x t +1 )  si  x t  est impair
Observer que la valeur de départ x 0 =1 donne la suite infinie périodique 1,2,1,2,1,
La valeur de départ x 0 =7 donne la suite 7,11,17,26,13,20,10,5,16,8,4,2,1,2,1,2,1,
La fameuse conjecture de Syracuse non démontrée à ce jour est que partant de toute valeur x 0 la suite arrive forcément au bout d'un moment T (qui dépend de x 0 ) à la suite périodique 1,2,1,2,
image: 61_home_faure_enseignement_Systemes_dynamiques_M1_Images_chap_intro_syracuse_10.png
Exercice 9.2.1. Ecrire un programme qui demande x 0 à l'utilisateur et affiche la suite ( x t ) t jusqu'à ce que la valeur x t =1 soit atteinte.
Solution 9.2.2. Faire:
  1. Ecrire une fonction S( x ) qui prend un entier x en entrée et renvoit 1 2 x si x est pair ou 1 2 ( 3x+1 ) sinon.
  2. Dans le programme principal, on demande x à l'utilisateur. Tant que x1 , faire
    1. x=S( x )
    2. Affiche x
 
Exercice 9.2.3. Pour x 0 donné, on note T( x 0 ) la plus petite valeur de t N telle que x t =1 . Tracer T en fonction de x 0 { 1,2, 1000 } .
Solution 9.2.4. Voici l'algorithme. Faire une fonction T=L( x ) qui prend un entier x en entrée et renvoit T :
  1. On pose T=1 .
  2. Tant que x1 faire:
    1. on calcule x=S( x ) .
    2. T=T+1
Ensuite,
  1. Créer une fenetre graphique, avec des axes x 0 =11000 et T=0100 ,
  2. on parcourt x 0 =11000 ,
    1. on calcule T=L( x 0 )
    2. on dessine un point en ( x 0 ,T )

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 m , longeur l , fixées, soumis à la force de pesanteur P=mg avec g=9.8m/ s 2 et à une force de frottement de coef γ .
image: 62_home_faure_enseignement_informatique_c++_cours_c++_cours_1_pendule.png
Equations physiques:
si a( t ) est la position angulaire et b( t ) = da dt la vitesse angulaire au temps t on a da dt = F a ( a,b ) :=b db dt = F b ( a,b ) :=- g l sin ( a ) - γ m b F a ( a,b ) , F b ( a,b ) sont des fonctions de a,b . (vérifier ces formules en appliquant la loi de Newton sur la droite verte tangente au cercle.).
Position du pendule:
x=l sin ( a ) ,y=-l cos ( a )
Méthode de résolution numérique
Avec la méthode de Euler, on considère un petit intervalle de temps dt ; au premier ordre en dt on a: a( t+dt ) =a( t ) +dt F a b( t+dt ) =b( t ) +dt F b En effet ces formules découlent de da dt = F a a( t+dt ) -a( t ) dt = F a a( t+dt ) =a( t ) +dt F a , et de même pour b .
Voici comment on utilise ces formules. On choisit un pas de temps dt très petit. Par exemple dt=0.01. On discrétise le temps avec un pas de temps dt : la date de départ est t 0 . ensuite il y a t 1 = t 0 +dt , t 2 = t 1 +dt , etc..
On suppose que a 0 =a( t 0 ) , b 0 =b( t 0 ) sont les conditions initiales connues. Avec les formules de Euler on calcule a( t 1 ) ,b( t 1 ) , puis a( t 2 ) ,b( t 2 )
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 a,b initiales, calculer a,b à 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 N=1 fois et que l'on impose au pli de faire un angle droit (90°) on obtient la forme suivante. Si on plie N=2 fois ou N=3 fois ou ... N=16 fois on obtient les formes suivantes assez surprenantes. La forme pour N1 s'appelle la fractale du dragon.
image: 63_home_faure_enseignement_informatique_c++_cours_c++_micro_projet_c_1.png image: 64_home_faure_enseignement_informatique_c++_cours_c++_micro_projet_c_2.png
image: 65_home_faure_enseignement_informatique_c++_cours_c++_micro_projet_c_3.png image: 66_home_faure_enseignement_informatique_c++_cours_c++_micro_projet_c_16.png
Ecrire un programme qui demande une valeur N1 et dessine la forme obtenue si l'on plie N fois. Qu'observez vous de surprenant?
Exercice 9.4.1. On peut coder la forme à N fixé, comme une suite S N de 2 N -1 valeurs ± 1 , où +1 (respect. -1 ) signifie que le pli est à droite (respect. à gauche). Par exemple S 1 ={ 1 } , S 2 ={ -1,1,1 } , S 3 ={ -1,-1,1,1,-1,1,1 } . Montrer que la suite S N+1 se déduit facilement de la suite S N .
Solution 9.4.2. Si la suite S N ( i ) est connue alors S N = S N-1 ¯ { 1 } S N-1 - S N-1 ¯ est la suite S N-1 é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. N1 est donné. On part de la liste S={ 1 } . On pose n=1 . Tant que n<N faire:
  1. On copie S'=S . On pose d=S.size( ) .
  2. On rajoute 1 au début de la liste S' .
  3. Pour i=0d-1 , on rajoute -S[ i ] au début de la liste S' .
  4. On copie S=S' . On fait n=n+1 .
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.
  1. On initialise ( x 1 , y 1 ) =( 0,0 ) et dir =0 et i=0 .
  2. On calcule ( x 2 , y 2 ) à partir de ( x 1 , y 1 ) de la façon suivante:
    1. Si dir =0 alors ( x 2 , y 2 ) =( x 1 +1, y 1 )
    2. Si dir =1 alors ( x 2 , y 2 ) =( x 1 , y 1 +1 )
    3. etc
  3. On change de direction: dir=( dir + S N ( i ) +4 ) %4 . (Remarque: on a écrit +4 car il y a un “bug” sur le modulo en langage C/C++.)
  4. On dessine une droite de ( x 1 , y 1 ) à ( x 2 , y 2 ) .
  5. On pose ( x 1 , y 1 ) =( x 2 , y 2 ) et i=i+1 .
  6. Si i< S N .size( ) 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 x=( x 1 , x n ) les variables d'espace et t la variable de temps. Joseph Fourier a découvert en 1822 que la propagation de la température T( x,t ) dans un matériau est régit par l'équation d'évolution T( x,t ) t =( Δ T ) ( x,t ) appelée équation de la chaleur, avec l'opérateur Laplacien: Δ T:= 2 T x 1 2 + + 2 T x n 2
On va modéliser cette évolution en dimension n=2 . On discrétise l'espace avec un pas h1 très petit. On note donc x 1 =ih , x 2 =jh avec i,j=0 N-1 . Le champ de températures à l'instant t est modélisé par une matrice T( i,j ) de taille N × N . On discrétise le temps avec un pas ε 1 très petit et on note T' le champ de température à l'instant t+ ε .
On remplaçant les dérivées par des différences finies montrer que l'équation de la chaleur ci-dessus devient:
T'( i,j ) =T( i,j ) + ε h 2 ( T( i+1,j ) +T( i-1,j ) +T( i,j+1 ) +T( i,j-1 ) -4T( i,j ) ) .
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 ε ,h . Pour les matrices, on utilisera la classe mat de armadillo.

9.7 Le jeu puissance 4

niveau facile (difficile si option de stratégie).

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.
image: 67_home_faure_enseignement_Systemes_dynamiques_M1_Images_chap_markov_Ising_beta_03.jpg image: 68_home_faure_enseignement_Systemes_dynamiques_M1_Images_chap_markov_Ising_beta_043.jpg image: 69_home_faure_enseignement_Systemes_dynamiques_M1_Images_chap_markov_Ising_beta_053.jpg
Figure 9.1: Résultat de l'algorithme de Monte-Carlo pour le modèle d'aimantation de Ising, aux températures β =1/( k b T ) =0.3 (désordre), 0.4 (transition de phase) et 0.5 (ordre magnétique).
(Explication physique: voir Cours de Master 1: système dynamiques, chapitre “Dynamique probabiliste sur les graphes”).
On considère un réseau N × N périodique dont les sites sont notés X=( x,y ) et dont les variables sont f X = ± 1 et modélisent des spins (up/down). Si deux sites sont voisins (chaque site a 4 voisins) on note XY . Une configuration des spins est un champ donné: f= ( f X ) X . Son énergie ferromagnétique est: E( f ) := X Y,XY ( - f X . f Y )
Remarque 9.9.1. Il y a N 2 sites et 2 N 2 configurations possibles.
Les configurations d'énergie minimale sont celles donnant ( - f X . f Y ) minimal soit f X . f Y =1 pour tous X,Y . Il y a donc deux configurations d'énergie minimale qui sont: f up ={ f X =1 pour tous site X } et f down ={ f X =-1 pour tous site X }.
Les configurations d'énergie maximale sont celles donnant ( - f X . f Y ) maximal soit f X . f Y =-1 pour tous X,Y . Cela est possible si N est pair avec des spins dont les valeurs sont alternées (il y a deux configurations).

9.9.1 Algorithme de Monte-Carlo

L'algorithme d'évolution suivant est appelé algorithme de montecarlo . β 0 est un paramètre fixé qui est l'inverse de la température: β =1/( k b T ) .
  1. A l'instant n=0 , on part d'une configuration f choisie au hasard.
  2. On choisit un site X 0 au hasard, et on note f' la configuration identique à f sauf au site X 0 où le spin est opposé: f ' X 0 =- f X 0 (spin opposé).
  3. Choix de la configuration à l'instant n+1 ,
    1. Si E( f' ) <E( f ) on choisit la configuration f' .
    2. Si E( f' ) E( f ) on choisit la configuration f' avec la probabilité P ff' = exp ( - β .( E( f' ) -E( f ) ) ) (et on choisit f avec la probabilité complémentaire).
  4. On revient en (2) pour poursuivre l'évolution du champ de spins.
Remarque 9.9.2. Cet algorithme correspond à une matrice stochastique réversible pour la mesure de Boltzmann: u( f ) = 1 Z exp ( - β E( f ) ) image: 70_home_faure_enseignement_Systemes_dynamiques_M1_Images_chap_markov_CIMG3409.JPG
Exercice 9.9.3.  
  1. Montrer que la condition E( f' ) <E( f ) s'écrit simplement en fonction du site X et de ses voisins.
  2. Ecrire un programme qui fait évoluer et représente la configuration du champ de spins f X 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”).
  3. L'aimantation de la configuration f est M( f ) := X f X . Représenter l'aimantation au cours du temps. Quand l'équilibre statistique est atteind, calculer la moyenne M et la variance V= ( M- M ) 2 . Représenter M ( β ) et V( β ) en fonction de β .

9.10 Zeros d'un polynome aléatoire

On considère un polynome P( x ) de degré N1 et à coefficients P k aléatoires et indépendants, selon une loi uniforme P k [ -1,1 ] : P( x ) = k=0 N P k x k
On sait que P a N zéros ( z i ) i=0N dans le plan complexe. Dessiner les N 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: P k = R k +i I k avec R k , I k [ -1,1 ] 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.
image: 71_home_faure_enseignement_informatique_c++_cours_c++_cours_1_zeros.png
sls
sls d

Partie II Compléments sur le langage C++

Chapitre 10 Autres environnements

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.
Remarque 10.1.1. Il existe aussi d'autres environnements de développement sophistiqués comme eclipse ou emacs.
Exercice 10.1.2.  
  1. Lancer le programme codeblocks.
  2. Choisir "create a new project”
  3. Faire les choix: projet de type "Console application”, C++; lui donner un nom, comme "projet1” et choisir le répertoire où il sera hébergé.
    Dans le répertoire “Sources”, il 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;
    }
  4. Une fois pour toute on va choisir la version 11 du C++. Pour cela, dans le menu Settings/Compiler/Compiler Flags, cocher l'option de compilation -std=c++11
  5. La commande du menu Plugins/SourceCodeFormatter vous permet de mettre votre programme en forme standard. Faites le souvent.
  6. Pour compiler et exécuter ce programme, taper F9 (ou build and run dans le menu). Il apparait alors une fenêtre (console) avec le message "Hello world!”.

Chapitre 11 Ecrire et lire des données en binaires sur un fichier (ou un flux)

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
Voici le taille des principales types de base:
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:
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++

12.3 Utiliser du code fortran en c++

Voir cette page web.

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

In the next example we call a routine in fortran to compute Bessel functions J ν ( z ) with complex order ν C and complex argument z C with π< arg zπ. , from Algorithm 912: A Module for Calculating Cylindrical Functions of Complex Order and Complex Argument. It gives good precision (less than 1 0 -14 ) if | Re v|60,| Im v|60,| Re z|300,| Im z|300.
This is not yet available in c++ or c, that provides J ν ( z ) 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

Exercice 13.1.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.
  2. Ecrivez le code pour effectuer le produit et la division externe d'un réel a avec un vecteur v (produit de chaque élément) par la syntaxe w=v*a; ou w=v/a; et testez.
  3. 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 à ( -v ) qui est l'opposé du vecteur v . Pour cela il faut écrire:
Code à rajouter dans le code des fonctions membres:
Vect operator-()
{
return (*this)*(-1);
}
Utilisation dans le programme principal:
int main()
{
Vect v(1,2,3);
Vect w;
w=-v;
w.Affiche();
}

13.1.4 Commentaires

Exercice 13.1.2.  

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 a 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

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 R 3 précédente, en permettant d'avoir des vecteurs de R N avec N quelconque, au choix de l'utilisateur.
Important: avant de commencer, nous conseillons vivement de (re)lire le paragraphe sur les pointeurs.
Il apparaît que les variables membres de la nouvelle classe, doivent être:
(Code à écrire dans la déclaration de la classe:)
//---- variables membres
int N; //taille
double *element; //pointeur
Important: la valeur de N nous renseigne à tout moment sur la taille de l'espace mémoire réservé.
Cette 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:

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 R 3 , 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.  
  1. Ecrivez le code du constructeur par recopie et de l'opérateur d'affectation = ci dessus, et testez. (en affichant des messages).
  2. Reprenez les fonctions membres de la classe Vect de R 3 précédente, et écrivez les versions “Vect de R N “ correspondantes.
    Concernant spécialement l'opérateur [ ], avec N éléments, on décide d'y accéder de la façon suivante:
    Vect v(N);
    v[0],v[1],...,v[N-1],
    Rem: Dans le code de l'opérateur [ ](int i), il est bien de tester si l'indice i est dans l'intervalle autorisé, et sinon d'afficher un message d'erreur (i.e. si i<0, ou i>N-1).
  3. Dans un deuxième temps, on aimerait que le premier indice i 1 soit quelconque (et pas forcément i 1 =0 ). Par exemple, que les trois composantes d'un vecteur de R 3 soit v[2],v[3],v[4],i.e. i 1 =2,N=3;
    Précisement, on voudrait qu'un vecteur soit caractérisé par sa taille N , et par son premier indice i 1 , afin d'accéder aux éléments du vecteur par la commande v[i] avec i ε [ i 1 , i 1 +1,..., i 1 +N ] .
    Il faut donc rajouter i 1 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.
  4. (Long) Sur le même modèle que la classe vecteur, créer une classe matrice, avec les opérations usuelles.
    Rem: on pourra dé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

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:

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;

}
Remarque 14.1.1. Au début du fichier main.cc, l'instruction #include "main.h" a pour effet d'inclure le contenu du fichier main.h à cet endroit. Il y a les déclarations (mais pas le code) de la classe Vect et de la fonction f qui sont utilisées dans la fonction main().
//== fichier 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;
}
Remarque 14.1.2. Le fichier autres.cc contient le code des fonctions membres de la classe Vect et le code de la fonction f(). Noter la syntaxe particulière Vect::Norme() qui signifie fonction Norme de la classe Vect.
//== 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
Remarque 14.1.3. Le fichier main.h contient les déclarations (et non pas le code) de la classe Vect et de la fonction f(). Les lignes particulières:
#if !defined(__MAIN_H)
#define __MAIN_H
...
#endif
ne sont pas nécessaires ici mais sont souvent utiles: ce sont des instructions du “préprocesseur” c'est à dire que au moment de la compilation, si le compilateur voit le code intermédiaire ... la première fois, il le considère (et met la variable __MAIN_H à 1). Les fois suivantes, il ne considère par cette partie de code. L'intérêt de cela est que si le 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
Remarque 14.1.4. Le première ligne signifie “on compile le fichier c++ main.cc et on fabrique un fichier en langage machine main.o”. De même pour la deuxièmre ligne. La troisième ligne signifie “à partir des fihciers main.o et autres.o on fabrique le fichier exécutable final main”.

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).
Voici un tutoriel sur le Makefile. Voici le manuel de make.
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
Remarque 14.2.1. Attention le décalage en début de ligne est une tabulation.
Commande de compilation
on écrit:
make final
 
résultat:
g++ -c main.cc -o main.o
g++ -c autres.cc -o autres.o
g++ main.o autres.o -o main
Commentaires:

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
Remarque 14.2.2.  

14.2.2 Generation automatique du fichier makefile?

Voir generation avec GNU

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:
A faire au cours du projet en local:

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:

14.5 Commenter et documenter un projet avec Doxygen

Nous expliquons l'utilisation de base de doxygen pour documenter automatiquement un projet en c++.
Documentation: Conseils de root du cern.

14.5.1 Création et consultation de la documentation

14.5.1.1 Création de la documentation html

14.5.1.2 consultation de la documentation

14.5.2 conventions pour écrire les commentaires

14.5.2.1 Page principale:

(voir mainpage)
Ecrire:
/*! \mainpage Titre
\section Nom_section
texte...
*/

14.5.2.2 Le résultat suivant:

image: 72_home_faure_enseignement_informatique_c++_cours_c++_cours_1_markdown_example.png
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.

Chapitre 15 Interface interactive pour le C++

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.
Voici en particulier une liste de librairies pour l'audio, le graphisme.

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.
Voici aussi la Documentation générale.

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.
image: 55_home_faure_enseignement_informatique_c++_cours_c++_cours_1_dessin_2D_de_base.png
#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

Référence sur les coordonnées.
Exemple:
image: 73_home_faure_enseignement_informatique_c++_cours_c++_cours_1_droites.png
 
#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

Avec la classe TCanvas qui hérite de TPad.
#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

Avec la classe TPad.
image: 74_home_faure_enseignement_informatique_c++_cours_c++_intro-root_frame.png
 
#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();
}
image: 75_home_faure_enseignement_informatique_c++_cours_c++_cours_1_drawframe_2.png

16.1.5 Un seul axe gradué: TGaxis

Avec la classe 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();
}
image: 76_home_faure_enseignement_informatique_c++_cours_c++_intro-root_axe.jpg

16.1.6 Couleurs

voir Couleurs
image: 77_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glc_iso_palette.png
#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 y=f( x ) : TF1

Avec la classe TF1.

16.2.1.1 Exemple à partir d'une formule littérale:

image: 78_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TF1.png
 
#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

image: 49_home_faure_enseignement_phys_stat_cours_figures_TB.png
#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 y( x ) à partir de tableau de valeurs y( i ) : TH1D

Avec la classe TH1D.
image: 79_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH1F.png
 
#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)

Voir TPie.
Même exemple ci-dessus avec l'option h1->Draw("PIE 3d"); et h1->Draw("PIE rsc");
image: 80_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH1_Pie_1.png image: 81_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH1_Pie_2.png

16.2.3 Représentation d'un tableau de valeurs y( i ) sous forme de “camembert” ou “Pie”

Voir TPie.
image: 82_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH1_Pie_3.png
#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 ( x( i ) ,y( i ) ) avec axes à partir de tableaux de valeurs x( i ) ,y( i ) : TGraph

Attention: si les valeurs x( i ) 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 x( i ) et y( j ) ne sont pas équidistribuées.
Avec la classe TGraph.
Voir Options de dessin.
Exemple:
image: 83_home_faure_enseignement_informatique_c++_cours_c++_cours_1_graph.png
 
#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 ( r( i ) , θ ( i ) ) représentée en coordonnées polaire à partir de points r( i ) , θ ( i )

image: 84_home_faure_enseignement_informatique_c++_cours_c++_cours_1_CPol2.png
Voir Options de dessin.
#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:
image: 85_home_faure_enseignement_informatique_c++_cours_c++_cours_1_CPol.png
Voir Options de dessin.
#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 y=f( x ) par tirage aléatoire et ajustement par une formule : TF1

Remarque 16.2.1. voir HowtoFit.html
image: 86_home_faure_enseignement_informatique_c++_cours_c++_cours_1_root_fit.png
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

Classe THStack.
image: 87_home_faure_enseignement_informatique_c++_cours_c++_cours_1_histo_superp.png
#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: z=f( x,y ) à partir d'un tableau de valeurs z( i,j ) : TH2D

Avec la classe TH2D.
image: 88_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH2D.png image: 89_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH2D_col.png image: 90_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH2D_box.png image: 91_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TH2D_lego.png
 
#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 z=f( x,y ) : TF2

Avec la classe TF2.
Voici le résultat du programme suivant avec différentes options, en particulier, représentation cylindrique, polaire, spherique et en utilisant openGL.
image: 92_home_faure_enseignement_informatique_c++_cours_c++_cours_1_TF2.png image: 93_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glsurf1.png image: 94_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glsurf3.png
image: 95_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glsurf1cyl.png image: 96_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glsurf1pol.png image: 97_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glsurf1sph.png
 
#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 z( x,y ) à partir d'un ensemble arbitraire de points x( i ) ,y( i ) ,z( i ) : TGraph2D

La classe TGraph2D permet de dessiner une surface z( x,y ) à partir de la donnée d'un ensemble de N points ( x i , y i , z i ) .
image: 98_home_faure_enseignement_informatique_c++_cours_c++_cours_1_Tgraph2d.png
#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

Doc TArrow.
image: 99_home_faure_enseignement_informatique_c++_cours_c++_cours_1_champ_de_vecteur.png
/// 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 R 3 : TPolyMarker3D

Doc: TPolyMarker3D.
Remarque: ensuite avec le menu on peut régler leur opacité etc
image: 100_home_faure_enseignement_informatique_c++_cours_c++_cours_1_polymarker3d.png
 
#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 f( x,y,z ) =0 : TF3

Ici la surface dans R 3 est déterminée par l'équation f( x,y,z ) =0 avec f 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 f( x ) =-0.2 et non pas f( x ) =0 .
image: 101_home_faure_enseignement_informatique_c++_cours_c++_cours_1_surface_1.png
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
image: 102_home_faure_enseignement_informatique_c++_cours_c++_cours_1_surface_2.png
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:
image: 103_home_faure_enseignement_informatique_c++_cours_c++_cours_1_surface_3.png
#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 f( x,y,z ) R : TH3

Doc: TH3.
Dessin avec openGL et en option: dessin de la surface de niveau de f (“gliso”) ou boules montrant la valeur de f (“glbox”) ou densité transparente “glcol”.
Remarques:
image: 104_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glc.png image: 105_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glc_box1.png image: 106_home_faure_enseignement_informatique_c++_cours_c++_cours_1_glc_col.png
#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 C= 1 N x N y N z i,j,k f( x i , y j , z k ) . Par conséquent si on souhaite montrer la ligne f( x,y,z ) = C 0 avec C 0 arbitraire, il faut par exemple modifier le contenu du point i,j,k=1,1,1 en écrivant f( x 1 , y 1 , z 1 ) =f( x 1 , y 1 , z 1 ) + δ avec δ =- i,j,k f( x i , y j , z k ) + N x N y N z C 0 de sorte que C= 1 N x N y N z ( i,j,k f( x i , y j , z k ) + δ ) = C 0
Voici un exemple qui dessine la surface x 2 + y 2 + z 2 =2 c'est à dire la sphère de rayon 2:
image: 107_home_faure_enseignement_informatique_c++_cours_c++_cours_1_sphere.png
#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.
image: 108_home_faure_enseignement_informatique_c++_cours_c++_cours_1_sphere_equateur.png
#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

Document in User guide, chap 18.
Compile with the option -lGeom -lRGL
Result of the next program:
image: 109_home_faure_enseignement_informatique_c++_cours_c++_cours_1_figures_Tgeom.png
#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.

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:
  1. Fichiers à télécharger et sauver dans son répertoire: bib_fred.h et bib_fred.cc.
  2. Il faut avoir installé le logiciel gifsicle,
 
#include <TApplication.h>
#include <TCanvas.h>
#include <TEllipse.h>
#include "bib_fred.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”.

16.8.1 Exemple simple avec quelques widgets

Voici un exemple plus simple, à copier/coller, donnant l'interface:
image: 110_home_faure_enseignement_informatique_c++_cours_c++_cours_1_fen_commandes.png
/*! ===========================
* 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;
}

Remarque 16.8.1.  
  1. 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

image: 111_home_faure_enseignement_informatique_c++_cours_c++_cours_1_widget_menu_tab.png
/*! ===========================
 * 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)

(taken from root forum)
image: 112_home_faure_enseignement_informatique_c++_cours_c++_cours_1_GUI_sous_tabs.png
#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.
  1. 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

Voir TRandom.
 
#include <TRandom.h>
gRandom->SetSeed(0); // initialise le hasard sur l'horloge
double x= gRandom->Uniform(2); // nombre aléatoire dans l'intervalle (0,2)

16.9.2 Trucs utiles avec les fenetres

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>

Chapitre 17 Mesure du temps

On présente ici la classe chrono.
Mais on peut aussi conseiller la classe Boost.Date_Time.

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

On propose d'utiliser la librairie odeint de boost. Voir aussi la documentation ODEINT ou Introduction a Odeint in boost.
Introduction:
Une équation différentielle ordinaire (E.D.O.) est une équation de la forme: dX dt =F( X,t ) X( t ) R n est un vecteur qui dépend du temps t R , et F: R n × R R n est une fonction connue. On connait X( 0 ) et F l'on cherche X( t ) pour d'autres valeurs de t R . Le théorème de Cauchy Lipchitz garantit l'existence et l'unicité de X( t ) mais on a rarement une expression explicite de la solution. L'ordinateur est utile pour trouver des valeurs approchées de X( t ) .
Remarque 19.0.1. Cette méthode permet en particulier de calculer l'intégrale d'une fonction. En effet, pour l'équation de la forme dX dt =F( t ) la solution est formellement X( t ) = 0 t F( t ) t .
Exemple
Pour intégrer avec la methode “runge_kutta_dopri5 with standard error bounds 10-6 for the steps”, le champ de vecteur dans X( t ) =( x 0 ( t ) , x 1 ( t ) , x 2 ( t ) ) R 3 du système de Lorenz défini par: d x 0 dt = σ ( x 1 - x 0 ) d x 1 dt =R x 0 - x 1 - x 0 x 2 d x 2 dt =-b x 2 + x 0 x 1 avec les paramètres: σ =10 , R=28 , b=8./3. , 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;
}
}
Remarque 19.0.2. Pour plus d'options ou autres méthodes d'intégration voir la documentation.
Exercice 19.0.3. Modifier le code ci-dessus pour dessiner la trajectoire dans le plan x 1 , x 2 avec un point tous les pas de temps dt=0.02 et t=0100 et obtenir l'image suivante:
image: 113_home_faure_enseignement_informatique_c++_cours_c++_cours_1_Lorenz.png
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

20.0.0.2 Travail préalable pour le projet C++

  1. Fichiers à télécharger et sauver dans son répertoire: bib_fred.h et bib_fred.cc.
  2. Dans codeblocks, il faut ajouter ces fichiers au projet en faisant dans le menu: Projet/add_files ...
  3. 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
#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:
image: 114_home_faure_enseignement_informatique_c++_cours_c++_cours_1_signal.png image: 115_home_faure_enseignement_informatique_c++_cours_c++_cours_1_signal2.png
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:

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
 
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)

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:

22.1.0.1 Travail préalable pour le projet C++

  1. Fichiers à télécharger et sauver dans son répertoire: bib_fred.h et bib_fred.cc, audio.h et audio.cc.
  2. Dans codeblocks, il faut ajouter ces fichiers au projet en faisant dans le menu: Projet/add_files ...
  3. Il faut aussi rajouter -larmadillo dans les options de compilation (pour codeblocks c'est dans Setting/Compiler/LinkerSettings/OtherLinkerOptions)
  4. Démarrage du serveur sndiod, si vous êtes administrateur, écrire dans un terminal: sudo sndiod -dd -z480 -f rsnd/0 et le laisser ouvert,
    sinon, si sndio a été installé sur votre compte, écrire dans un terminal: 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 Dt=1/48000sec. et envoie les données à la carte son qui les convertit sous forme numérique. Cet exemple lit les données du micro depuis la carte son par bloc de N=1000 donnée, soit de durée T=N.Dt=0.02 sec., et les dessine au fur et à mesure.
image: 116_home_faure_enseignement_informatique_c++_cours_c++_Fonctions_de_Fred_s.png image: 117_home_faure_enseignement_informatique_c++_cours_c++_Fonctions_de_Fred_s2.png
 
#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 T , et fréquence f=1/T dans l'intervalle audible 30Hzf3000Hz . 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.
image: 118_home_faure_enseignement_informatique_c++_cours_c++_cours_1_periode_extrait_WF.png
#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:
  1. Les threads: au niveau de la terminologie, chaque programme s'appelle un “thread” ou “process” ou “context”. On va tout d'abord apprendre à démarrer des threads.
  2. Les mutex: lorsque ces programmes doivent “communiquer” c'est à dire échanger des données (et c'est souvent le cas) il y a des précautions à prendre 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).
  3. Les variables conditionnelles, cond_var: c'est comme une sonnette. Cela permet au processeur de mettre en attente certains thread et d'en réactiver d'autres sous certaines conditions.
références: article sur C++11 threads, locks and condition variables.

23.1 Lancement de threads ou processus

23.1.1 Exemple

23.1.1.1 Commande de compilation:

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

  1. 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.
  2. On peut passer (ou pas) des variables à la fonction lancée en parallèle, ici on passe la variable n.
    1. 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.
    2. Mais pour passer une variable par référence, il faut écrire par exemple, void f(int & n) {..} pour la déclaration et thread t(f,ref(n)); pour le lancement.
  3. Il peut être utile de créer une certaine attente dans un programme. Utiliser Section 17.2.
  4. Si vous oubliez t.join() à la fin, il y aura une erreur à l'execution du programme.
  5. 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

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
Remarque 23.2.1.  

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:
  1. La commande
          mtx.lock();

    qui signifie:
  2. La commande
          mtx.unlock();

    qui signifie: on met mtx à “libre”
Remarques

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

  1. Pour bloquer plusieurs mutex simultanément: Doc on scopped_lock, “A tour of c++”, 2018, page 418:
    scoped_lock lck {mutex1,mutex2,mutex3};
  2. 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;
  3. 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:
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

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,...
Remarque 23.4.1.  

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,
...

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
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>
Remarque 24.1.1. Dans la premiere ligne, on donne l'adresse du programme CGI situé sur le serveur. Ensuite il y a du code html avec des widget qui ont chacun un nom. Si on clique sur le widget Submit, cela a pour effet d'envoyer les données de chaque widget au serveur.

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:
// -*- 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)”.
Voir cette explication ou ici.
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

Voir JQuery Post method

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;
    }
 
Voir aussi cet exemple de cgicc.

Chapitre 25 Sockets: communications entre process via le réseau

Référence:

25.1 Réseaux

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:
  1. Stream socket (Transmission Control Protocol, TCP) : garantit que l'échange est bien effectué (renvoit un code d'erreur sinon).
  2. 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.
  3. Raw socket: pour développeurs.
  4. 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:
  1. 2-tier: communication: client<->serveur directe. Cela peut être risqué.
  2. 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: IP=( N 1 , N 2 , N 3 , N 4 ) , N i [ 0,255 ] , avec les conventions suivantes.

25.2.1.1 Classes d'adresses

Selon la valeur de N 1 , (en base 2 et * signifie une valeur quelconque): N 1 = ( 0* ) 2 classe A
N 1 = ( 10* ) 2 classe B N 1 = ( 110* ) 2 classe C N 1 = ( 1110* ) 2 classe D, pour multicasting N 1 = ( 1111* ) 2 classe E, non utilisé.
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.
Fonctions utiles pour manipuler les adresses IP.

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:
image: 119_home_faure_enseignement_informatique_c++_cours_c++_cours_1_socket_client_server.gif

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:

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)

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
Résultat:
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
midisyn /home/faure/alex_ratchov/patches/midisyn.conf
//===== 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)
Remarque 26.3.1. La liste des codes des instruments est donnée d'après la convention “general midi”. Le canal 9 est réservé aux percussions. Mais si vous utilisez le synthetiseur midisyn, pour le moment il n'y a que les possibilités suivantes:
code program_change
0
1
2
3
4
33
73
74
75
76
77
instrument
piano
guitare
guitare
guitare
guitare
bass
flute
flute
flute
flute
flute
et spécialement sur le canal 9 des percussions:
instrument:
kick
stick
snare1
snare2
hihatc
hihatf
hihato
crash1
ride1_bell
ride1
splash
crash2
sqr
sqr
key:
36
37
38
40
42
44
46
49
51
53
55
57
67
68
Le détail des sons est configurable dans les fichiers patches/midisyn.conf patches/piano/piano.conf etc... On peut par exemple rajouter de nouveaux sons en ajoutant les fichiers raw correspondant et un fichier de configuration associé.
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
//===== 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.
ref: ici ou ici ou la référence.
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:

27.1.1 Premier bloc (header chunk)

Il est composé de

27.1.2 Bloc de piste (Track chunk)

Il est composé de
La fin de bloc est annoncée par le message avec 3 octets: xFF, x2F, x00

27.1.3 Message Midi standard M

M a 2 ou 3 octets M= x 1 , x 2 , x 3 . Le premier octet est décomposé en 2 quartet (4 bits chacuns) x 1 =qc c[ 0,15 ] est le numéro de canal. Les octets suivants sont 0 x 2 , x 3 <128
x 1
signification et nombres d'octets à suivre
x 2
x 3
Messages standard
x8c
Note est éteinte sur un canal c
note: C 0 G 10
durée de l'extinction, 0:lent
x9c
Note est jouée sur canal c
note
intensité. ( 0 : éteind)
xAc
Aftertouch sur canal c (vibrato sur une note)
note
force du vibrato
xBc
Control Change sur canal c (paramétrage d'un canal) cf détails en 27.1.7.
type
valeur
xCc
Program Change sur canal c (changement d'instrument sur un canal)
instrument
-
xDc
Canal pressure sur canal c (vibrato sur un canal)
vibrato
-
xEc
Pitch Wheel Change (glissando sur un canal) décale de δ = x 2 +128 x 3 -64.128 .
x 2
x 3

27.1.4 Message méta évènement M (pour les signaux midi sur fichier et non pas temps réel)

x 1
signification
x 2
x 3 ,
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)) M

x 1
signification
x 2
x 3 ,
xF0
System Exclusive Message (SysEx)
xF7
Le message se termine par le même octet 0xF7:

27.1.6 Message temps réel M (pour les signaux midi temps réel et non sur fichier)

x 1
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 x 1 = xBc , x 2 = type , x 3 = valeur

(copié de jchr)
Valeurs possible du type x 2
 
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
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
2F
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)

Constructeurs
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,
`pkg-config --cflags --libs opencv`
Remarque 29.1.1. Si vous n'utilisez pas codeblocks, mais Makefile par exemple, la compilation en ligne de commande du programme main.cpp pour donner l'executable main est:
g++ main.cpp -o main -g -std=c++11 `pkg-config --cflags --libs opencv`

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:

29.2.0.1 Fonctions utiles sur les images

Copie d'une image:
Recadrage d'une image:

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
    
}
Remarque 29.4.1. La commande waitKey(0) permettrait de faire avancer la video image par image.

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.
image: 122_home_faure_enseignement_informatique_c++_cours_c++_cours_1_objet_bleu_trouve.jpg

#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

Voici des informations, sur les algorithmes MeanShift et CamShift , ou sur wikipedia.
image: 123_home_faure_enseignement_informatique_c++_cours_c++_cours_1_cam_shift_demo.png
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

Ref: 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

31.5 Mécanique des fluides, EDP non linéaires

Chapitre 32 Emscripten: conversion d'un programme C++ en programme Java Script

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

Voir Installation avec Portable SDK.
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/
Voir tutorial

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:
Lien: code.html.
image: 124_home_faure_enseignement_informatique_c++_cours_c++_cours_1_javascript_1.png
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
Remarque 32.3.1. Dans cette commande on rajoute _ au nom de la fonction.
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.
Remarque 32.3.2.  

32.3.2 Echange de tableaux

Références: Interacting-with-code.html et emscripten-pointers-and-pointers.html
Résultat:
Lien: code_array.html.
image: 125_home_faure_enseignement_informatique_c++_cours_c++_cours_1_code_emscripten_code_array.png
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:
Lien: code.html.
image: 126_home_faure_enseignement_informatique_c++_cours_c++_cours_1_javascript_4.png
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>
Resultat

32.5 Autres remarques

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>
Resultat

32.7 Utilisation de librairies ?

Lire ceci.
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

reference html5.h.

32.8.1 Animation and Timing

Reference: 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)

Result and code c++:
// -*- 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.
Voici un exemple.

32.10.1 Fichiers sources

Voici le code source de l'exemple et des librairies.

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,
  1. Soit de compiler le code avec g++ et d'obtenir un exécutable. Il écrit automatiquement:
  2. Soit de compiler le code avec emscripten et d'obtenir un code en wasm qui tourne sur un navigateur.

33.1 Mode d'emploi

en partant d'un fichier "nomfichier.cc" qui contient le main().
Pour l'utiliser, il faut
  1. écrire le fichier makef.config
  2. lancer : makef repertoire nomfichier.cc makef.config <nom_executable>
  3. 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

 
class Com;
extern Com *p_com; // pointeur sur l'objet (declare dans .cc)
extern mutex mtx;

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:
Les widgets sont placés à la suite horizontalement dans la zone demandee.
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

Lire licences GPL.
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:
  1. 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.
  2. 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