L'unique but de ce petit guide consiste à énumérer quelques petits trucs
utiles vous évitant le suicide après quelques séances
(ça permet également aux assistants d'aller boire une bière
travailler ardemment, mais évitons des polémiques inutiles).
Attention !
C'est un peu long et fatiguant à lire sur un écran, donc je vous conseille
d'imprimer cette page, ou du moins de lire en priorité les paragraphes
marqués d'un
« »
Si besoin est, écrivez-moi un e-mail,
je me ferais un plaisir de vous répondre... ...en tout cas avant Pâques !
Ce qu'il vaut mieux savoir sur l'ordinateur
Voici quelques postulats (pour les physiciens) ou axiomes
(pour les mathématiciens) de base à propos d'un ordinateur.
Un ordinateur est obtus
On peut se convaincre aisément de cette affirmation en se rappelant
qu'un ordinateur est un automate programmable, comme vous la dit le
Prof. Chappelier dans son cours.
C'est-à-dire qu'il ne va comprendre ou faire que ce qu'on lui a dit
de comprendre ou de faire, ni plus ni moins. C'est pas gagné, mais
on va lui expliquer lentement et clairement, histoire qu'il comprenne.
Pour la programmation, cela signifie que même si vous trouvez que
remplacer les points virgules à la fin de chaque instruction par des
W majuscules est la plus importante innovation artistique depuis le
cubisme, l'ordinateur risque malgré lui d'étouffer dans l'oeuf
votre immense potentiel créatif.
Respectez toujours la syntaxe au signe et à la lettre près
quand vous programmez.
Il suffit d'un espace en trop, ou d'un « n »
à la place du « m » dans
« main » par exemple,
pour qu'il y ait une erreur à la compilation.
Beaucoup d'erreurs sont de ce type lorsqu'on débute en programmation et
qu'on ne connaît pas encore bien « l'orthographe »
du langage.
Un ordinateur est profondément obtus
C'est-à-dire qu'il ne se corrige pas (le vilain).
Afin d'éviter de le corriger vous-même à coup de hache,
sachez être magnanime envers moins ouvert que vous, et démontrez ainsi
la supériorité de l'Humain sur la machine.
En programmation, une erreur est invariante par rapport aux translations
dans le temps et [habituellement] dans l'espace.
Ce qui signifie que si deux messages d'erreurs donnés par le compilateur
sont identiques, alors les erreurs le sont aussi (la plupart du temps),
quel que soit le référentiel considéré.
Autrement dit : l'espace des erreurs est isotrope et
homogène (c.f. Mécanique Générale, Gruber et Benoît, p. 661)
On refait souvent les mêmes erreurs.
Essayez de regarder à quoi correspondent les messages d'erreur que vous
obtenez fréquemment ; vous pourrez ainsi identifier immédiatement
certaines bourdes récurrentes.
Un ordinateur est sournois et vicieux
Ceci ne veut pas dire qu'une caméra numérique se trouve sous chaque chaise
dans la salle d'info (quoique c'est une idée à creuser ?).
Résumons : vous êtes en face d'un adversaire froid, insensible et
capable de résoudre une équation aux dérivées partielles alors que vous
téléchargez avidement le dernier mp3 de Britney Spears (non ce n'est pas
du vécu).
Si je vous dis qu'en plus, il se joue de vous avec une facilité
déconcertante, là vous ressortez la hache à deux mains et un
lance-roquettes avec. Mais le/la Sage sait déjouer les vils pièges tendus
par la machine rusée et malveillante.
Parfois, certaines erreurs ne sont pas ce qu'on croit qu'elles sont
(what is the Matrix?).
La réalité transcende la fiction et cette fichue compilation ne veut
toujours pas se faire, pourtant je regarde à la bonne ligne, je connais
le message d'erreur et j'ai beau scruter avec mes bésicles en argent du
19e siècle, je n'y vois écrit qu'une ligne de code à première
vue parfaitement juste. C'est grave docteur ?
Non ! Deux cas peuvent se présenter :
Ça fait deux heures que vous programmez et que vous regardez
votre écran : la fatigue, la lassitude et l'ennui sont là
=> vous ne distinguez plus la faute.
En vertu des postulats 1) et 2), le compilateur va parfois vous
signaler une erreur à une ligne X, alors que le vrai problème
se situe à la ligne précédente, voir même avant. C'est assez
embêtant, mais avec de l'entraînement, on arrive à s'en sortir.
Quand vous ne trouvez pas d'où vient l'erreur, demandez
à votre voisin de regarder à votre place. Un autre
regard sur votre problème peut parfois faire des miracles.
Et comme on [=les assistants] vient de finir notre bière
de tout à l'heure de répondre aux questions d'un autre
étudiant, on peut aussi venir y mettre notre grain de sel.
Regardez la ligne qui précède celle où le
compilateur a détecté l'erreur et vérifiez s'il ne manque pas
un point-virgule, une parenthèse, un crochet ou autre chose
d'anormal. Si vous ne trouvez rien, remontez plus haut,
etc.
Servez-vous également de l'indentation et de la mise en évidence
syntaxique d'(X)Emacs pour détecter le problème.
Essayer de comprendre ce que le compilateur vous
dit, en vous aidant au besoin de votre voisin, ou d'un
assistant (s'il en reste un pas trop assoifé
surchargé).
On signalera aussi que certaines erreurs dépendent les unes des autres.
Certaines d'entres elles peuvent d'ailleurs en entraîner toute une
cascade : quand vous oubliez ou quand vous faîtes une erreur dans
la syntaxe de la déclaration des bibliothèques au début du programme
par exemple.
Si la bibliobliothèque est mal déclarée (i.e. mal inclue) -> le compilateur
ne la trouve pas -> quand ensuite, il rencontre des commandes définies dans
la biblio et utilisées dans votre programme, il ne les comprend pas, car
elles n'ont pas été définies dans votre programme, et, en vertu du postulat
1), il ne sait pas où les chercher.
Exemple :
l'oubli de «#include <iostream>»
dans le programme entraînera une erreur sur quasiment chaque appel à
cin, cout, endl, ...
Voilà une petite marche à suivre, qui peut être appliquée non seulement
pour réaliser l'exercice 4 de la série 3, mais aussi la plupart des
exercices et séries qui viennent ensuite.
Rappel de l'énoncé :
Écrivez un programme age.cc qui :
demande son âge à l'utilisateur ;
lit la réponse de l'utilisateur et l'enregistre dans une
variable age de type entier ;
calcule l'année de naissance (à un an près) de l'utilisateur
et l'enregistre dans la variable annee
de type entier ;
affiche l'année de naissance ainsi calculée.
Compilez et exécutez votre programme.
Squelette de base
Commencez systématiquement votre programme en écrivant le squelette suivant :
#include
using namespace std;
int main()
{
}
Naturellement, si votre programme ne comporte pas d'entrées/sorties (ce qui est
rare pour un programme, mais arrive fréquemment en compilation séparée), il n'est
pas nécessaire d'include <iostream>.
Par ailleurs, si vous savez déjà que vous devrez utiliser des fonctions de la
bibliothèque mathématique, des chaînes de caractères, des vectors, etc.
vous pouvez également préciser les autres bibliothèques dont vous aurez besoin
(<cmath>, <string>,
<vector>, ...).
Déterminer les variables
Essayer de comprendre le but de l'exercice : quel(s) calcul(s)
le programme doit-il faire, quelles sont les variables du problème ?
Par exemple, dans le cas de l'exercice considéré, l'énoncé stipule :
« lit la réponse de l'utilisateur et l'enregistre dans une
variable age de type entier »
il faut donc une variable, de type entier, appellée
age.
« calcule l'année de naissance (à un an près) de l'utilisateur
et l'enregistre dans la variable annee de type entier »
il faut donc une (seconde) variable de type entier, appellée
annee.
Ce sont les seules variables utiles pour ce programme.
Remarque :
Comme il n'y a pas de valeurs initiales pertinentes pour ces variables,
(c'est l'utilisateur qui, lors du fonctionement du programme, précisera
ce qui peut tenir lieu de valeurs initiales), et qu'il n'y a pas de raison
d'utiliser ces variables avant leur première affectation, il n'est pas
obligatoire de les initialiser lors de la déclaration (bien que cela
soit tout de même conseillé).
Le programme devient alors
(rappel : entier -> int) :
#include
using namespace std;
main()
{
int age; // variable identifiée en (1), non initialisée !
int annee(0); // variable identifiée en (2), initialisée à 0
/*
* ATTENTION: PAS D'ACCENTS DANS LES NOMS DE VARIABLES,
* DE FONCTIONS, DE TYPES, ..., ETC.
*/
}
Réfléchir aux entrées/sorties
De façon générale, les question à se poser sont :
Qu'est ce que le programme doit demander à l'utilisateur (cout) ?
Quelles valeurs doit-il récupérer de l'utilisateur (cin) ?
Quel/s résulat/s doit/vent être affiché/s (cout) ?
Essayons d'y répondre :
Ce que le programme doit demander à l'utilsateur ?
L'énoncé précise :
« demande son âge à l'utilisateur »
Le programme devra donc afficher une invite du genre
"Quel age avez-vous ?"
Remarque :
pour afficher quelque chose à l'écran, il faut utiliser
le stream (flot) [de sortie] cout.
On aura donc l'instruction suivante :
cout << "Quel âge avez-vous ? ";
Ce que le programme doit récupérer de l'utilsateur ?
Toujours avec cette partie de l'énoncé :
« demande son âge à l'utilisateur »
après avoir explicité à l'utilisateur ce que l'on attend de lui (point précédent),
il faut récupérer (i.e. lire) la valeur qu'il indiquera via le clavier,
et mémoriser cette valeur dans la variable age.
Remarque :
Pour lire quelque chose depuis le clavier, il faut utiliser
le stream (flot) [d'entrée] cin.
On aura donc l'instruction suivante :
cin >> age;
Les résultats à afficher ?
L'énoncé précise :
« affiche l'année de naissance ainsi calculée »
en fin de programme, après que tous les calculs nécessaires aient été réalisés,
il faut afficher à l'écran la valeur contenue dans la variable
annee
Il faut naturellement faire préceder cette valeur par un descriptif de ce qui
est affiché, de sorte à obtenir par exemple :
Vous êtes né environ en 1980
Affichage qui se décompose en fait en deux parties :
"Vous êtes né environ en ",
une chaîne de caractères littérale
1980 la valeur de la variable
annee
On aura donc l'instruction suivante :
cout << "Vous êtes né environ en " << annee;
Remarque :
Pour améliorer la lisibilité du résultat, on ajoutera un saut de ligne
après l'affichage de la valeur.
On obtient finalement l'instruction :
cout << "Vous êtes né environ en " << annee << endl;
L'état de notre programme devient maintenant :
#include
using namespace std;
main()
{
/* déclaration des variables */
int age; // variable entière, non initialisée
int annee(0); // variable entière, initialisée à 0
/* demande & lecture/stockage de l'âge de l'utilsateur */
cout << "Quel âge avez-vous ? "; // affichage de l'invite
cin >> age; // lecture+stockage de la valeur saisie au clavier
/* calculs */
/* affichage des résultats */
cout << "Vous êtes né environ en " << annee << endl;
}
Résoudre le problème (effectuer les calculs)
La résolution du problème est souvent l'étape la plus difficile,
bien que dans le cas présent, les calculs soient triviaux :
On sait que l'année de naissance est environ égale à l'année
actuelle (2002) moins l'âge de la personne.
Comme on souhaite de plus stocker le résultat de ce calcul dans
la variable annee, on obtient l'instruction :
annee = 2002 - age;
On obtient au final le programme suivant :
#include <iostream>
using namespace std;
main()
{
/* déclaration des variables */
int age; // variable entière, non initialisée
int annee(0); // variable entière, initialisée à 0
/* demande & lecture/stockage de l'âge de l'utilsateur */
cout << "Quel âge avez-vous ? "; // affichage de l'invite
cin >> age; // lecture+stockage de la valeur saisie au clavier
/* calculs */
// la variable age étant maintenant définie, on peut procéder au calcul:
annee = 2002 - age;
/* affichage des résultats */
cout << "Vous êtes né environ en " << annee << endl;
}
Les quelques directives ci-après ne sont ni des règles strictes,
ni des recommandations à suivre absolument. Néanmoins, si l'on est
débutant en C++, il est préférable de s'y conformer, afin de conserver
une certaine uniformité et lisibilité à ses programmes.
Mise en page du code source
Un programme informatique est un ensemble structuré d'éléments
déclaratifs (par exemple le prototypage des fonctions) et d'instructions.
En C++, une bonne part de cette structuration nait de la notion de
.
Veillez à ce que vos blocs et instructions soient
identifiables au premier coup d'oeil
(ce qui rendra plus lisible la structure de vos programmes).
Pour cela, les quelques règles de mise en forme suivante peuvent suffirent :
Après chaque instruction (presque toujours terminées par
« ; »),
insérez un retour à la ligne, avant de commencer
une nouvelle instruction.
Essayer de trouver un juste milieu entre les instructions trop
longues et les retours à la ligne dans les instructions ;
dans le cas idéal (pas toujours réalisable malheureusement),
une instruction est courte et n'occupe qu'une ligne
(i.e. une ligne ne compte pas plus de 50~80 caractères;
si l'instruction est plus longue, ecrivez-la sur plusieurs
lignes).
Utilisez l'indentation (i.e. les retraits à gauche)
pour refleter l'imbrication des blocs :
après chaque ouverture de bloc, augmentez l'indentation à gauche
d'un nombre fixe d'espaces (typ. 4) et diminuez cette indentation
du même nombre d'espaces au moment de refermer le bloc.
Débutez chaque bloc simple ou associé à un élément déclaratif
(p.ex. les fonctions) en plaçant l'accolade ouvrante sur une
nouvelle ligne.
Débutez chaque bloc associé à une structure de contrôle en plaçant
l'accolade à la fin de la ligne initiant cette structure.
Terminez chaque bloc en plaçant l'accolade fermante correspondante
sur une nouvelle ligne.
Exemple :
double f(const int x);
double g(const int x)
{
double h0(f(x)), h1(0);
double v0;
if ( f(x) >= 0 ) {
h1 = sqrt(f(x));
do {
...
...
} while ( h1 > h0 );
}
return v0;
}
Contre-exemple (strictement équivalent pour le compilateur) :
double f (const
int x); double
g(const int x) { double h0(f(x)),h1(0); double v0;
if (f(x)>=0){h1=sqrt(f(x));do{ ...
... } while (h1>h0);}return v0;}
Utilisez les fonctionnalités de l'éditeur Emacs
pour vous aidez à mettre en forme votre code : il se chargera
de mettre des retours de lignes après chaque instruction,
ainsi que de l'indentation des blocs, pour autant que vous preniez
l'habitude d'appuyer sur la touche Tab
au début de chaque ligne.
Il existe une quantité d'autres règles et conventions pour l'écriture des
programmes... voyez en particulier
ici.
Nommage
les variables débutent par une minuscule, les mots adjoint par une majuscule.
Exemple :
int variable;
char variable1;
double maVariable;
double maSuperVariable(2.214);
...
les constantes sont écrites en majuscules, les mots adjoints séparés par un souligné.
Exemple :
const int CONSTANTE(0);
const int CONSTANTE2(1);
const double MA_CONSTANTE(2.0);
const double VALEUR_DE_PI(3.14);
...
Commentaires
N'hésitez pas à indiquer à l'aide de commentaires ce à quoi une fonction
est censé servir, comment faut-il l'utiliser (en commentant le prototypage
de la fonction), quel algorithme (i.e. méthode de résolution) est employé
pour implémenter la fonction (en commentant la définition de la fonction),
et, sans doute le plus important, qu'est-ce que les variables
modélisent (en commentant la déclaration de ces variables).
Exemple :
void rebond(double& h, double& v, const double esp, const double g = 9.81);
//
// Simule un (et un seul) rebond de balle.
//
// Entrées: h = la hauteur de la balle avant le rebond
// v = la vitesse de la balle avant le rebond
// eps = le coefficient de rebond balle/sol
// [opt] g = la valeur de la gravité
//
// Retour: -
//
// Sorties: h = la hauteur après rebond
// v = la vitesse après rebond
// Retour:
// -
// V.Globale:
// g
...
void rebond(double& h, double& v, const double esp, const double g)
{
// le rebond est simulé à l'aide de ...
// avec v[sol]^2 = (2hg) et ...
double g2(g*g); // carré de la gravité (acc. calculs)
double f(eps*v*v); // coeff. de frottement balle-air
double a(0); // accélération de la particule
...
}
Structures de contrôle
N'utilisez pas les clauses des structures de contôle pour faire
- au passage - autre chose que ce pour quoi la clause est réservée,
et évitez au maximum des sorties ou branchements non standard pour
la structure (i.e. les break,
continue, return
ou exit).
les clauses d'initialisation, test de continuation et mise à jour des boucles
for ne contiennent que ce qui est strictement nécessaire
au déroulement (sens strict) de la boucle (i.e. ce qui structure l'itération) ;
évitez en particulier d'introduire du code devant normalement faire partie du bloc
itéré au niveau de la clause de mise à jour.
for (
«initialisation»;
«test de continuation»;
«mise à jour) { ... }
avec :
initialisation
en général, déclaration et initialisation de la variable utilisée
pour contrôler l'itération (compteur) ;
test de continuation
en général, un test portant sur l'état de la variable contrôlant
l'itération par rapport à d'autres grandeurs ;
mise à jour
en général, une instruction d'incrément ou de décrément
de la variable de contrôle de d'itération.
Utiliser une variable initialisée à 0 dans la clause d'initialisation
et incrémentée de 1 dans la clause de mise à jour permet de disposer
à tout moment d'un compteur indiquant le nombre de fois que le bloc
associé au for à été exécuté.
Exemple :
for (int i(0); i < 10; i++) {
...
}
Contre-exemple :
for (v2=2*v1-5,v1=0; ; cout << v2--) {
...
if (...) break;
else continue;
}