Remédiation en C++

Les bases du C++

Université de Bordeaux – Licence Ingénierie Mathématique

Si vous avez suivi le tuto jusqu’ici, vous avez compilé et fait tourner un premier programme simple. Rentrons maintenant dans la syntaxe même du langage C++. Il y aura une bonne partie de «cours» avant qu’on remette les mains dans le code.

Nous allons voir:

  • ce qu’est une instruction, un bloc d’instructions, des commentaires
  • une variable, un type de variable, et quels sont les types de base en C++,
  • comment écrire les nombres,
  • et la structure minimale d’un code C++ (la fonction main)

Il y aura un QCM à la fin, et on écrira un programme ensemble pas à pas, c’est pour cela que le nombre de diapos en bas à droite peut paraître impressionnant ^^’

Les instructions

Une instruction est une opération qui sera réalisée par le programme.

En C++ chaque instruction se termine par un point-virgule ;.

On peut écrire plusieurs instructions sur une même ligne, et une instruction peut être écrite sur plusieurs lignes. Et on peut rajouter autant d’espaces que l’on veut entre les mots et les symboles. Voilà quelques exemples:


int i=0; 

         double a = -10.; double b = 10;

std::cout << "Cela s'affiche" << std::endl
          << "a l'ecran"      << std::endl;
         

Il y a plein de conventions de mises en forme qui sont possibles, qui diront comment écrire, aligner, etc… Ce qui compte, c’est que votre code soit lisible ! Et si vous êtes plusieurs à travailler sur les mêmes fichiers, il vaut mieux se mettre d’accord sur les règles à suivre.

Les blocs

Les intructions peuvent être regroupées dans un bloc. En C++, un bloc commence et finit par des accolades {}. Les blocs peuvent être imbriqués.

// cette ligne est en dehors d'un bloc

{
  // celle-ci est dans un premier bloc
}

{
  {
      // Un bloc dans un bloc
  }
}

On peut considérer un bloc comme étant une seule instruction qui en regroupe plusieurs.

Les commentaires

Sur la slide précédente, j’ai écrit des commentaires pour donner des informations sur le code.

Les commentaires sont très importants dans un programme. Ils permettent de faire comprendre ce que fait un code qui devient souvent très compliqué. Les codes restent rarement utilisés par une seule personne, ou alors il arrive de rouvrir un code qu’on a écrit des années auparavant et dont on se souvient pas. Il faut donc savoir transmettre l’information de manière efficace: il faut suffisamment de commentaires, mais pas trop, et assez clairs même pour une personne qui repart de (presque) zéro (mais qui connaît le langage).

En C++, les commentaires s’écrivent de deux façons:

  • soit sur une seule ligne, après deux slashes
  • soit sur une ou plusieurs lignes, entre les balises /* et */
// un commentaire tout seul
int a = 1; // un commentaire après une instruction

/*
   un bloc de commentaires
*/

int b=2; /* un
 autre
 bloc de commentaires */ int c=3;

Les variables

Les variables sont des emplacements dans la mémoire de l’ordinateur, qui servent à stocker des valeurs. Par exemple, des nombres, des caractères, etc.

Chaque variable a un nom, de la même manière qu’on donne un nom en mathématiques à une valeur numérique x ou une fonction f.

Le principe de fonctionnement des programmes est de modifier les valeurs des variables à l’aide d’algorithmes.

Quand je représenterai les variables dans la suite du tuto, je le ferai comme ceci:

avec le nom de la variable au dessus, et la valeur stockée à cet endroit dans la case. Ici, j’ai représenté un nombre réel x qui vaut -2.5.

Les variables

En C++, il y a quelques règles pour les noms de variables. Un nom de variable:

  • est en seul mot
  • ne peut commencer que par une lettre ou un underscore _
  • n’a pas d’accents ou de caractères exotiques (valable pour tout le reste du code aussi)
  • peut contenir des chiffres (ailleurs qu’en première position)
  • est sensible à la casse, ce qui veut dire que les majuscules et miniscules d’une même lettre sont considérées comme des caractères différents. La variable truc et la variable Truc ne sont pas les mêmes.

Choisissez bien vos noms de variables. Comme pour les commentaires, il doivent être assez clairs, mais pas trop lourds non plus.

Par exemple, si vous devez garder en mémoire le nombre de fois qu’une opération a réussi, vous pourrez appeler votre variable successCount ou encore nSuccess, mais évitez n ou number_of_times_the_function_succeeded.

Les types

Lorsque j’ai parlé des variables, j’ai dit qu’elles occupaient un emplacement mémoire. Mais en fonction de ce qu’on veut stocker, la place prise en mémoire sera différente. Par exemple, il faut plus de bits (0 et 1) pour représenter un nombre réel que pour représenter un nombre entier.

C++ fait partie des langages pour lesquels il faut indiquer au compilateur quel est le type de variable associé au nom de la variable. Pour d’autres langages ce n’est pas obligatoire (Python par exemple), mais cela peut créer de très mauvaises surprises!

Une règle très importante en C++ est qu’une variable doit être déclarée avant toute utilisation. Au moment de la déclaration, on donne le type de la variable, et ce type ne changera pas durant la suite du programme.

C’est pour cela qu’on dit que C++ est un langage typé statiquement.

Déclarer une variable

Chaque variable doit être déclarée avant sa première utilisation.

Cela se fait en suivant cette syntaxe:

type nom;

où il faudra remplacer le type et le nom par ceux qu’on souhaite. On peut cumuler la déclaration d’une variable avec son initialisation, c’est à dire le fait de lui donner une première valeur. C’est une très bonne pratique qui évite beaucoup de bugs!

type nom = valeur;
// ou
type nom(valeur);

// Exemples:
int a = 1;
double x = -2.5;
std::string s("coucou");

Nous allons maintenant voir quelques uns des types qui sont prédéfinis dans le langage C++, pour quels usages, et comment écrire les valeurs qu’ils peuvent prendre.

Les types : quelques types prédéfinis - entiers

Commençons par les entiers. Le type vraiment basique est int. Mais il existe aussi:

  • long : entiers codés sur plus de bits, pour représenter des entiers plus grands en valeur absolue que les int
  • unsigned int, unsigned long : entiers non signés, c’est à dire forcément positifs. C’est pratique pour représenter des indices dans des tableaux ou des algorithmes
  • d’autres variantes de long long, short, …

Ça fait beaucoup de possibilités pour représenter les entiers! Même trop, et pour compliquer l’affaire, le nombre de bits en mémoire auquel ces types correspondent change suivant la machine!

Mon conseil est le suivant: pour les tutos et les cours, n’utilisez que int et éventuellement unsigned int.

Pour un programme pour lequel la taille des entiers est importante, utilisez int32_t et int64_t, qui disent explicitement leur taille.
  - Les int32_t sont dans l’intervalle [−2 147 483 648; +2 147 483 647],
  - Les int64_t dans l’intervalle [−9 223 372 036 854 775 808; +9 223 372 036 854 775 807].

Les nombres constants entiers s’écrivent tels quels: 1, 2, -10. Vous pourrez parfois trouver les suffixes l et u pour signifier que ces valeurs doivent être utilisées en temps qu’entiers longs et non-signés, respectivement: 1234567889012l, 2u.

Les types : quelques types prédéfinis - nombres réels

Les nombres réels sont représentés en interne avec l’écriture à virgule flottante:

signe * mantisse *2exposant

Les types de base les plus courants pour les réels sont:

  • le type float , avec une précision d’environ 7 chiffres significatifs
  • le type double, qui utilise deux fois plus de bits, a une précision d’environ 16 chiffres significatifs.

IMPORTANT. Étant donné que les nombres réels ne peuvent souvent pas être représentés de façon exacte à cause du nombre limité de bits sur lesquels ils sont codés, c’est dangereux de tester l’égalité stricte de deux réels. L’égalité est définie à la précision machine près. Si on note cette précision ε, alors on regarde si |a-b|< εmin(|a|,|b|) plutôt que de tester si a=b. Il y aura plusieurs exemples dans les programmes et QCM de ces cours.

Pour écrire les réels en C++, on peut utiliser

  • l’écriture flottante: 1., -2.3, 0.00454, .05 (on peut retirer le 0 avant la virgule)
  • l’écriture exponentielle: 1.e0, -2.3e0, 4.54e-3, 5.e-2

Attention avec les valeurs entières pour des nombres réels. La constante “3” sera considérée comme un entier, et pas un réel. Si on veut que ce soit un réel, on peut simplement rajouter un point: “3.”. On verra plus tard pourquoi ça peut créer des problèmes.

Les types : quelques types prédéfinis - booléens

Les booléens sont un type à part qu’on rencontre dans les programmes car ils sont très simples. Ils ne peuvent prendre que deux valeurs: vrai ou faux.

En C++ le type des booléens est bool, vrai et faux s’écrivent true et false, sans majuscules.

Les types associés aux nombres entiers peuvent être considérés comme des booléens. Dans ce cas on associe 0 à false, et toutes les autres valeurs (positives ou négatives) à true. On en reparlera quand on verra les expressions, d’ici quelques slides.

Les types : quelques types prédéfinis - caractères et chaînes de caractères

Un autre type très utile est celui qui sert à écrire directement des mots.

À la base, en C, on trouve le type char qui sert à encoder un seul caractère.

char c = 'a';
char d = '1'; // Attention, ce n'est pas le nombre entier 1, mais le caractère pour écrire le chiffre 1

Mais on veut écrire des mots, c’est-à-dire une suite de caractères. En C, il y a une façon de les représenter qui se base sur les tableaux, mais qui n’est pas très pratique.

En C++, il a les std::string qui sont des objets qui permettent de manipuler les chaînes de caractères bien plus facilement. Ces chaînes de caractères ne sont pas présentes nativement dans un programme, il faut les inclure depuis la bibliothèque string.

#include <string>

// ...

std::string s0("Hello");
std::string s1 = "World!";

On reviendra sur l’utilisation des std::string au fur et à mesure du tuto.

La durée de vie d’une variable

Nous pouvons maintenant déclarer des variables de types usuels. Cependant, il faut savoir qu’une variable n’existera pas forcément pendant toute la durée d’exécution du programme.

Les variables ont ce qu’on appelle une portée, c’est-à-dire une durée de vie.

La portée d’une variable est limitée au bloc d’instructions dans laquelle elle est déclarée.

Prenons l’exemple suivant:

{ // debut bloc 1

  unsigned int i = 0u;

  { // debut du bloc 1.1
    double x = -1.;
    { // debut du bloc 1.1.1
      unsigned int j=0u;
    } // fin du bloc 1.1.1
    
  } // fin du bloc 1.1
  
  { // debut du bloc 1.2
    unsigned int i = 1u;
  } // fin du bloc 1.2
  
} // fin du bloc 1

La durée de vie d’une variable

{ // debut bloc 1

  unsigned int i = 0u;

  { // debut du bloc 1.1
    double x = -1.;
    { // debut du bloc 1.1.1
      unsigned int j=0u;
    } // fin du bloc 1.1.1
    
  } // fin du bloc 1.1
  
  { // debut du bloc 1.2
    unsigned int i = 1u;
  } // fin du bloc 1.2
  
} // fin du bloc 1

À la ligne 3, dans le bloc 1, on déclare un entier positif i, qui vaut 1. Quand on rentre dans le bloc 1.1 à la ligne 5, i existe toujours, car on n’est pas sorti du bloc 1.

La durée de vie d’une variable

{ // debut bloc 1

  unsigned int i = 0u;

  { // debut du bloc 1.1
    double x = -1.;
    { // debut du bloc 1.1.1
      unsigned int j=0u;
    } // fin du bloc 1.1.1
    
  } // fin du bloc 1.1
  
  { // debut du bloc 1.2
    unsigned int i = 1u;
  } // fin du bloc 1.2
  
} // fin du bloc 1

À la ligne 8, les entiers positifs i et j existent, ainsi que le nombre réel x. On est dans les blocs 1.1.1, 1.1 et 1.

En revanche, à la ligne 10, la variable j n’existe plus : elle a été déclarée dans le bloc 1.1.1, et on en est sorti. Idem ligne 12, les variables j et x ne sont plus connues.

La durée de vie d’une variable

{ // debut bloc 1

  unsigned int i = 0u;

  { // debut du bloc 1.1
    double x = -1.;
    { // debut du bloc 1.1.1
      unsigned int j=0u;
    } // fin du bloc 1.1.1
    
  } // fin du bloc 1.1
  
  { // debut du bloc 1.2
    unsigned int i = 1u;
  } // fin du bloc 1.2
  
} // fin du bloc 1

J’ai ajouté le bloc 1.2 pour illustrer une bizarrerie du langage. Vous pouvez déclarer deux variables qui ont le même nom, du moment qu’elles ne sont pas déclarées dans le même bloc. Évidemment, je déconseille de le faire! Mais sachez que ça existe, et que ça peut être une source de bugs!

La durée de vie d’une variable

On va tester ça: téléchargez ce code : porteeVariables.cpp. C’est le même qu’à la slide précédente, mais j’ai ajouté ce qu’il faut pour que ça compile.

Compilez le et exécutez le. Vous devriez avoir ça:

On peut voir qu’une fois sorti du bloc 1.2, la valeur de i est revenue à 0. En fait, la deuxième variable n’écrase pas la première. Les deux variables ne partagent pas le même espace mémoire. Ce qui fait qu’en sortant du bloc 1.2, la seconde variable i a été détruite, mais la première reste.

Les opérateurs

Les opérateurs sont des symboles qui définissent des opérations sur une variable (opérateur unaire) ou entre deux variables (opérateur binaire). Voici les principaux opérateurs, par priorité décroissante. Il en existe d’autres.

  • ++ --  (unaires) : incrémentation, décrémentation. Pour les entiers faire i++; revient à faire i=i+1;.
  • + - ! not  (unaires): pour modifier le signe d’une variable ou d’une constante (-3.5, -i). Le point d’exclamation est équivalent à not. Tous deux servent à exprimer la négation pour les booléens.
  • bool isOk = false;
    std::cout<< !isOk << std::endl; // affichera 1, car `not isOk` est vrai
  • * / %  (binaires) : multiplication, division, reste de la division entière)
  • < > <= >=  (binaires) : comparaison
  • == !=  (binaires) : comparaison, pour tester l’égalité et l’inégalité.
  • && and  (binaires) : ET logique. Les deux opérateurs sont équivalents.
  • || or  (binaires) : OU logique. Les deux opérateurs sont équivalents.
  • = += -= *= /= %=  (binaires) : affectation, affectation avec une opération. a += 2; revient à faire a = a+2;

Ne confondez pas le test d’égalité == et le signe d’affectation =.

Les notations &&, || et ! sont héritées du C. Je préfère utiliser and or et not en toutes lettres car c’est plus lisible.

Les opérateurs

Le comportement des opérateurs change en fonction du type de(s) opérande(s).

Notamment, l’exemple le plus courant est celui de la division. Je ne compte plus le nombre de points que j’ai retirés à cause de ça.
Si les deux opérandes du symbole / sont des entiers, alors c’est la division entière qui est réalisée.
Si au moins une des deux opérandes est réelle, alors c’est la division réelle qui est faite.

int N = 1000;
std::cout << 1 /N << std::endl;
std::cout << 1./N << std::endl;

Dans cet exemple, au premier affichage la constante “1” est considérée comme entière, N est entier, donc le résultat est 0. Il y a 0 fois 1000 dans 1. Dans le second cas, on fait la division réelle car la constante “1.” est réelle.
Si on veut faire la division réelle de deux variables entières, on peut faire ça de plusieurs manières:

int a=1; int b=2;
std::cout << a/(1.*b) << std::endl; // methode "sale", on multiplie une des deux variables par le reel 1.
                                    // Ainsi (1.*b) est reel, et on effectue la division reelle.
std::cout << a/(double)(b) << std::endl; // methode "propre", on effectue un "cast" de la variable b 
                              // en une variable de type double. Une variable temporaire est creee, 
                              // b reste un entier apres l'operation. L'avantage de faire ceci
                              // est qu'on voit clairement le mot "double" dans le code, ce
                              // qui montre sans aucun doute possible qu'on veut faire la division reelle

La fonction main

C’est bientôt fini pour cette partie! Il ne nous reste plus qu’un élément nécessaire pour qu’un programme en C++ puisse être compilé.

Il s’agit de la fonction main. On reviendra plus tard sur la notion de fonction, mais on peut déjà parler de celle-ci parce qu’elle est très particulière.

int main() {
  // on fait des trucs
  // ...
  // ...
  return 0;
}

Tout programme C++ doit avoir une et une seule fonction main. Il s’agit en fait du point d’entrée dans le programme. Les instructions commencent à être exécutées à partir de ce point du programme.

L’entier 0 retourné à la fin du programme est un signal envoyé au système d’exploitation, qui a lancé le programme, pour dire que le programme s’est déroulé normalement, sans erreur.

Voilà pour la partie cours, je vais ensuite poser quelques questions d’exercice pour voir si vous avez bien retenu et compris ce que j’ai présenté.

QCM

Question 1/4

Je compile le code suivant, et le compilateur m’envoie cette erreur:

#include <iostream>

int main() {
  double a=2  double b=3; 
  
  std::cout << "La somme vaut " << a+b << std::endl;
  return 0;
}

Qu’est-ce qui cause cette erreur ?

• Une variable n’est pas initialisée
 
• Il manque un point-virgule

• Le compilateur ne sait pas ce qu’est un double


QCM

Question 1/4

Je compile le code suivant, et le compilateur m’envoie cette erreur:

#include <iostream>

int main() {
  double a=2  double b=3; 
  
  std::cout << "La somme vaut " << a+b << std::endl;
  return 0;
}

Qu’est-ce qui cause cette erreur ?

• Une variable n’est pas initialisée
 
• Il manque un point-virgule

• Le compilateur ne sait pas ce qu’est un double


J’ai oublié un point-virgule qui complèterait la déclaration de a: double a=2;. Tant que le compilateur ne voit pas de point-virgule, il continue, et doit donc comprendre ce que veut dire “2 double”. Ça n’a pas de sens en C++, donc il dit qu’il y a une erreur à cet endroit, même si l’oubli est juste avant.

QCM

Question 2/4

Dans un programme, je dois afficher l’heure à laquelle un train arrive, si je connais son heure de départ, sa vitesse moyenne et la distance à parcourir.

Quel type de variable vais-je utiliser pour manipuler le résultat à afficher en heure et minutes ?

• std::string
 
• int
 
• double

Quel type de variable puis-je utiliser pour calculer le résultat à une minute près avant l’affichage ?

• std::string
 
• int
 
• double


QCM

Question 2/4

Dans un programme, je dois afficher l’heure à laquelle un train arrive, si je connais son heure de départ, sa vitesse moyenne et la distance à parcourir.

Quel type de variable vais-je utiliser pour manipuler le résultat à afficher en heure et minutes ?

• std::string
 
• int
 
• double

Quel type de variable puis-je utiliser pour calculer le résultat à une minute près avant l’affichage ?

• std::string
 
• int
 
• double


L’affichage à l’écran se fait à l’aide de caractères. Surtout si je veux un bel affichage avec un format (hh:mm). On utilisera donc une chaîne de caractères.
Pour calculer le résultat, on aura sans doute un opération qui impliquera des nombres réels (vitesse*distance). On peut stocker le résultat dans un float ou un double, mais également dans un int, ce qui tronquerait la partie décimale du nombre. Ce ne serait pas grave, parce que le résultat est à une minute près.

Pour votre culture, il existe en fait un type time_t qui a été spécifiquement conçu pour gérer les données temporelles.

QCM

Question 3/4

J’ai eu 11, 9 et 9 à mes contrôles dans un module. Je calcule la moyenne en faisant

#include <iostream>
int main() {
  int n1(11), n2(9), n3(9);
  std::cout << "Ma moyenne est de " << (n1+n2+n3)/3 << std::endl;
  return 0;
}

Et ce programme me dit que j’ai 9, alors qu’en fait j’ai 9,66 et je pourrais espérer être remonté à 10!

Quel est le souci ?

• J’ai mal déclaré les variables
 
• La division est entière
 
• Je ne peux afficher que des entiers


QCM

Question 3/4

J’ai eu 11, 9 et 9 à mes contrôles dans un module. Je calcule la moyenne en faisant

#include <iostream>
int main() {
  int n1(11), n2(9), n3(9);
  std::cout << "Ma moyenne est de " << (n1+n2+n3)/3 << std::endl;
  return 0;
}

Et ce programme me dit que j’ai 9, alors qu’en fait j’ai 9,66 et je pourrais espérer être remonté à 10!

Quel est le souci ?

• J’ai mal déclaré les variables
 
• La division est entière
 
• Je ne peux afficher que des entiers

On peut déclarer et initialiser plusieurs variables d’un même type en une fois sur un même ligne, pas de souci. (j’avoue je ne vous l’avais pas dit avant) En revanche, l’erreur est dans la division, car (n1+n2+n3) est une addition d’entiers, donc un entier. “3” est une expression constante entière, donc la division réalisée est entière, et le résultat tronqué par rapport à la valeur réelle. Il aurait fallu écrire “3.” au dénominateur, ou mieux, utiliser des réels pour les notes, car elles peuvent avoir des demi-points!

QCM

Question 4/4

Laquelle de ces expressions utiliser pour tester l’égalité de deux float a et b ? (std::abs calcule la valeur absolue de l’argument entre parenthèses, et std::min le minimum de deux nombres donnés en argument)

• a==b
 
• std::abs(a-b)<1.e-7*std::min(std::abs(a),std::abs(b))

• (a-1.e-7)==(b-1.e-7)
 
• std::min(a,b)<1.e-7


QCM

Question 4/4

Laquelle de ces expressions utiliser pour tester l’égalité de deux float a et b ? (std::abs calcule la valeur absolue de l’argument entre parenthèses, et std::min le minimum de deux nombres donnés en argument)

• a==b
 
• std::abs(a-b)<1.e-7*std::min(std::abs(a),std::abs(b))

• (a-1.e-7)==(b-1.e-7)
 
• std::min(a,b)<1.e-7

On ne peut pas tester directement l’égalité de deux nombres réels en programmation, car ils ne sont définis qu’avec une certaine précision: 7 chiffres après la virgule pour les float.

On va donc plutôt tester si l’écart entre a et b est inférieur à cette précision, relativement à la valeur de a ou de b.

Oui c’est long à écrire, mais on verra comment faire pour que ce soit plus court par la suite :)

Écriture d’un programme pas à pas

Pour conclure cette partie, on va écrire un programme entier, pas à pas. N’hésitez pas à l’écrire vous même en parallèle, pour que ça rentre dans les doigts.

Ce programme demandera à l’utilisateur de rentrer 3 paires de coordonnées \(x\) et \(y\), et dira si le triangle formé par les 3 points est rectangle.

La première question à se poser est comment faire en termes mathématiques ? Si on appelle ces points A,B,et C, on calculera trois produits scalaires:

  • \((AB,AC) = (x_b-x_a)(x_c-x_a)+(y_b-y_a)(y_c-y_a)\)
  • \((AB,BC) = (x_b-x_a)(x_c-x_b)+(y_b-y_a)(y_c-y_b)\)
  • \((AC,BC) = (x_c-x_a)(x_c-x_b)+(y_c-y_a)(y_c-y_b)\)

Si un des trois produits scalaires est nul, alors le triangle est rectangle.

Écriture d’un programme pas à pas

Un programme C++ doit toujours comporter une fonction main. Commençons par ça. Écrivez ceci dans un fichier, que vous pouvez enregistrer sous le nom `triangleRectangle.cpp``, par exemple.



int main() {










  return 0;
}

Écriture d’un programme pas à pas

Il va ensuite falloir demander à l’utilisateur d’entrer des informations au clavier, et ensuite afficher un résultat à l’écran. On va donc lire depuis l’entrée standard, et écrire dans la sortie standard. Cela se fait en C++ avec les objets std::cin et std::cout, qui sont les flux depuis l’entrée et vers la sortie standard. Ils sont définis dans la bibliothèque iostream. Incluons donc cette bibliothèque.

#include <iostream>

int main() {










  return 0;
}

Écriture d’un programme pas à pas

Il faut demander à l’utilisateur des infos, qui sont les coordonnées des points. Ce sont donc 6 nombres réels dont nous aurons besoin, déclarons les:

#include <iostream>

int main() {
  double xa(0.e0),ya(0.e0),xb(0.e0),yb(0.e0),xc(0.e0),yc(0.e0);

  








}

Même si ce n’est pas nécessaire, je les initialise à 0 par habitude. J’ai eu trop de bugs à cause de variables non initialisées ! Même si c’est souvent le cas, rien ne garantit qu’un double non-initialisé vaut 0.

Rappel : “0.e0” est juste la notation exponentielle du réel 0, qu’on peut aussi écrire “0.”.

Écriture d’un programme pas à pas

Ensuite, les valeurs doivent être lues depuis le clavier. Ça se fait comme ça:

#include <iostream>

int main() {
  double xa(0.e0),ya(0.e0),xb(0.e0),yb(0.e0),xc(0.e0),yc(0.e0);

  std::cin >> xa >> ya >> xb >> yb >> xc >> yc;







  return 0;
}

Écriture d’un programme pas à pas

Si on laisse ça tel quel, l’utilisateur verra que le programme tourne, mais ne saura pas nécessairement quoi faire. Ajoutons un peu d’interactivité en indiquant poliment ce qu’il faut rentrer. On affiche une phrase à l’écran avant de lire avec std::cin.

#include <iostream>

int main() {
  double xa(0.e0),ya(0.e0),xb(0.e0),yb(0.e0),xc(0.e0),yc(0.e0);
  std::cout << "Entrez svp 3 paires de coordonnees 2D" << std::endl;
  std::cin >> xa >> ya >> xb >> yb >> xc >> yc;







  return 0;
}

std::endl est un objet de C++ qui permet d’effectuer un retour à la ligne dans les flux de sortie. Il n’y a pas besoin de std::endl dans les flux d’entrée comme std::cin. Essayez de compiler et faire tourner le programme avec et sans std::endl pour voir la différence.

Écriture d’un programme pas à pas

Les coordonnées sont lues. Calculons à présent les produits scalaires. Ce sont aussi des nombres réels, donc utilisons le type double.

#include <iostream>

int main() {
  double xa(0.e0),ya(0.e0),xb(0.e0),yb(0.e0),xc(0.e0),yc(0.e0);
  std::cout << "Entrez svp 3 paires de coordonnees 2D" << std::endl;
  std::cin >> xa >> ya >> xb >> yb >> xc >> yc;
  double scal1  = (xb-xa)*(xc-xa)+(yb-ya)*(yc-ya);
  double scal2  = (xb-xa)*(xc-xb)+(yb-ya)*(yc-yb);
  double scal3  = (xc-xa)*(xc-xb)+(yc-ya)*(yc-yb);




  return 0;
}

Écriture d’un programme pas à pas

Maintenant il faut dire si ces produits scalaires sont nuls. Comme ce sont des nombres réels, on va plutôt vérifier qu’ils ne sont pas plus petits (en valeur absolue) qu’un nombre correspondant à la précision du type double.

Je vais donc comparer deux doubles. Le résultat de cette évaluation sera vrai ou faux. Je vais mettre ce résultat dans une variable de type bool, pour chaque produit scalaire.

#include <iostream>

int main() {
  double xa(0.e0),ya(0.e0),xb(0.e0),yb(0.e0),xc(0.e0),yc(0.e0);
  std::cout << "Entrez svp 3 paires de coordonnees 2D" << std::endl;
  std::cin >> xa >> ya >> xb >> yb >> xc >> yc;
  double scal1  = (xb-xa)*(xc-xa)+(yb-ya)*(yc-ya);
  double scal2  = (xb-xa)*(xc-xb)+(yb-ya)*(yc-yb);
  double scal3  = (xc-xa)*(xc-xb)+(yc-ya)*(yc-yb);
  bool isOrtho1 = std::abs(scal1) < 1.e-16;
  bool isOrtho2 = std::abs(scal2) < 1.e-16;
  bool isOrtho3 = std::abs(scal3) < 1.e-16;

  return 0;
}

Quand je fais isOrtho1 = std::abs(scal1) < 1.e-16, l’opérateur “<” est prioritaire à l’assignation “=”. Le programme évalue donc d’abord std::abs(scal1) < 1.e-16 qui vaut soit vrai soit faux, et le résultat est ensuite mis dans isOrtho1.

Écriture d’un programme pas à pas

Il n’y a plus qu’à afficher le résultat !

#include <iostream>

int main() {
  double xa(0.e0),ya(0.e0),xb(0.e0),yb(0.e0),xc(0.e0),yc(0.e0);
  std::cout << "Entrez svp 3 paires de coordonnees 2D" << std::endl;
  std::cin >> xa >> ya >> xb >> yb >> xc >> yc;
  double scal1  = (xb-xa)*(xc-xa)+(yb-ya)*(yc-ya);
  double scal2  = (xb-xa)*(xc-xb)+(yb-ya)*(yc-yb);
  double scal3  = (xc-xa)*(xc-xb)+(yc-ya)*(yc-yb);
  bool isOrtho1 = std::abs(scal1) < 1.e-16;
  bool isOrtho2 = std::abs(scal2) < 1.e-16;
  bool isOrtho3 = std::abs(scal3) < 1.e-16;
  std::cout << "Le triangle est rectangle ? (1: vrai, 0: faux)   " 
            << (isOrtho1 or isOrtho2 or isOrtho3) << std::endl;
  return 0;
}

Écriture d’un programme pas à pas

Une perruche teste son programme, ca tourne mal Dernière étape, essentielle : TESTER. Même si “tester, c’est douter”, il faut toujours tester !

Compilez votre programme, et essayez de rentrer des coordonnées pour des triangles rectangles et des triangles qui ne le sont pas.

Pour faire un test idéal, il faut essayer plusieurs cas qui fonctionnent, et plusieurs qui ne fonctionnent pas.



Et voilà qui conclut cette première partie ! Dans la suite, nous verrons ce qu’on appelle les structures de contrôle, qui permettent de ne pas réaliser toutes les instructions les unes à la suite des autres.

Retour au cours Moodle