Cours de programmation en C pour débutants

Cours de programmation en C pour débutants

Ce cours a pour but de vous enseigner les bases du langage C, suffisantes pour vous permettre de commencer l'algorithmique.

Contrairement à de nombreux cours ou livres, nous ne nous contentons pas de vous apprendre les différents éléments du langage. Notre principal objectif est de vous apprendre à bien programmer, et nous vous donnons de nombreux conseils pour écrire vos programmes plus simplement et avec moins de risques de laisser des bugs. N'hésitez donc pas à lire l'ensemble du cours même si vous connaissez déjà une partie du langage C....

Comme pour bien apprendre, rien ne vaut la pratique, ce cours est accompagné de nombreux exercices, dont certains peuvent être corrigés automatiquement par notre système d'évaluation. Il est important de résoudre un maximum de ces exercices, car ce n'est qu'en essayant par vous mêmes que vous apprendrez réellement. Parfois, la solution est simplement fournie juste en dessous de l'énoncé, séparée par une ligne horizontale. Ne soyez pas tentés, et ne regardez pas la solution avant d'avoir cherché vous-mêmes, ou vous risquez de ne pas retenir grand chose.

Si vous rencontrez des difficultés sur l'un des exercices, n'hésitez pas à contacter un entraîneur pour demander de l'aide, en fournissant votre login et votre code source.

Pour tester vos programmes chez-vous, nous vous conseillons d'utiliser l'environnement de développement pour Windows Code::Blocks, entièrement gratuit. Notre Introduction à Code::Blocks décrit comment l'installer, puis l'utiliser pour les exercices de ce cours. Si vous êtes sous Linux, utilisez tout simplement gcc.

Le cours est écrit par Mathias Hiron, et inspiré en partie du cours de Caml, d'Arthur Charguéraud. Six chapitres sont actuellement disponibles :

Chapitre 1 : Instructions, variables, tests, boucles.

1.1 Instructions

1.1.1 Affichage de texte

Pour commencer, voici le programme C le plus simple qui fasse quelque-chose. Il se contente d'afficher le texte Hello World !. C'est traditionnellement le premier programme que l'on réalise lorsque l'on apprend un nouveau langage :

#include 
 
int main()
{
   printf("Hello World!");
   return 0;
}

On peut remarquer que ceci est un peu compliqué, pour un programme qui ne fait qu'afficher que deux mots. Il faut en effet connaître un certain nombre de notions du langage C pour comprendre tous les détails de cet exemple.

La ligne printf("Hello World!"); est ce que l'on appelle une instruction du langage : un ordre simple que l'ordinateur doit exécuter. On parle ainsi d'exécuter un programme, pour demander à l'ordinateur d'exécuter les instructions qu'il contient. Pour commencer, seule la ligne qui affiche effectivement le texte nous intéressera. Nous verrons plus tard le rôle des autres lignes, que l'on considérera pour l'instant comme des lignes à ajouter systématiquement avant et après le programme que nous écrirons.

Tous les programmes que vous devrez créer dans un premier temps auront donc la forme :

#include 
 
int main()
{
   /* Les instructions de votre programme */
   return 0;
}

Dans notre premier programme, la seule instruction qui nous intéresse actuellement est donc :

printf("Hello World !");

Cette instruction permet d'afficher à l'écran le texte fourni entre les guillemets et nous pouvons remplacer le texte Hello World! par n'importe-quel autre texte, comme par exemple Bonjour Monde ! :

printf("Bonjour Monde !");

Le principe de base du fonctionnement d'un ordinateur est qu'il exécute des instructions les unes après les autres. Bien qu'il ne soit pas directement le langage que comprend la machine, le langage C en est assez proche, dans ses principes de fonctionnement. Ainsi, les instructions d'un programme C sont exécutées les unes après les autres, dans l'ordre.

Exercice : Essayez de prévoir ce que fera le programme suivant, puis compilez-le, et lancez le pour vérifier :

 
#include 
 
int main()
{
   printf("Hello World !");
   printf("Hello World !");
   return 0;
}

Comme prévu, ce programme affiche deux fois de suite le texte "Hello World !". Il les affiche cependant bout à bout, sur la même ligne et sans séparation. Pour aller à la ligne entre les deux affichages, il est nécessaire d'afficher un caractère spécial permettant un retour à la ligne, qui se note "\n". Le programme suivant affiche donc nos deux messages sur deux lignes différentes :

 
#include 
 
int main()
{
   printf("Hello World !");
   printf("\n");
   printf("Hello World !");
   return 0;
}

Pour comprendre ce qui se passe lorsque ce programme est exécuté, on applique donc la règle suivante :

Effectuer les instructions une par une, dans l'ordre, dans le sens de la lecture.

Il est aussi possible d'écrire le même programme en n'utilisant qu'un seul printf, tout simplement en concaténant les trois morceaux de texte : printf("Hello World !\nHello World !");.

1.1.2 Affichage de nombres

L'instruction printf permet également d'afficher des nombres. On peut bien sûr exécuter printf("42"); pour afficher le nombre 42, mais c'est en fait le texte "42" qui est fourni à l'instruction printf, et non le nombre 42. Pour afficher réellement un nombre, il faut utiliser la syntaxe suivante :

 
#include 
 
int main()
{
   printf("Le nombre est %d\n", 42);
   return 0;
}

Ce programme affichera le texte "Le nombre est 42", suivi d'un retour à la ligne. Les caractères "%d" situés dans la chaîne indiquent en effet l'endroit de la chaîne où le nombre doit être inséré. Le nombre qui doit y être placé doit être fourni juste après la chaîne, séparé par une virgule.

La différence entre afficher un nombre, ici 42, ou le texte correspondant, "42", est que le premier permet d'afficher le résultat de ce qu'un ordinateur fait le mieux : des calculs. En effet, si en plus d'afficher le nombre 42, on souhaite afficher son successeur, 42 + 1, il suffit d'exécuter le programme suivant :

 
#include 
 
int main()
{
   printf("Le nombre est %d\n", 42);
   printf("Le successeur de ce nombre est %d\n", 42 + 1);
   return 0;
}

Pour effectuer des calculs en C, on écrit une expression qui ressemble à ce que l'on écrit en mathématiques, ou à ce que l'on tape sur une calculatrice. Par exemple, l'expression 3 + 4 - 2 signifie que l'on ajoute 4 à 3, puis retire 2, et vaut donc 5. L'instruction correspondante pour afficher le résultat est :

printf("Le résultat de 3 + 4 - 2 est %d\n", 3 + 4 - 2);

Cet exemple permet de bien différencier l'affichage d'une chaîne de l'affichage d'un nombre, puisque le texte "3 + 4 - 2" contenu dans la chaîne est laissé inchangé, alors que l'opération 3 + 4 - 2 placée après la virgule est calculée et que c'est son résultat (5) qui est affiché.

Exercice : écrivez un programme qui affiche le texte "Le résultat du calcul de 423 moins 117 est X", suivi d'un retour à la ligne, où X est remplacé par le résultat de l'opération, que vous devez laisser l'ordinateur calculer.


Solution :

 
#include 
 
int main()
{
   printf("Le résultat du calcul de 423 moins 117 est  %d\n", 423 - 117);
   return 0;
}

Remarque : si vous tapez votre source sous Windows, puis exécutez votre programme dans une fenêtre DOS, les accents ne s'afficheront pas correctement. Le problème est que Windows et Dos ne stockent pas les accents de la même manière. Si vous rencontrez ce problème, utilisez simplement du texte sans accents, ce n'est pas important pour ce cours.


D'autres opérateurs que + et - sont utilisables. Par exemple le caractère "*" représente l'opérateur de multiplication et le caractère "/" représente l'opérateur de division entière (qui donne le résultat de la division, sans les décimales). On peut également manipuler des nombres négatifs en plaçant un - devant ces nombres.

Comme en mathématiques, certains opérateurs sont prioritaires aux autres : si l'on écrit l'expression -2 + 5 * 6, l'ordinateur calcule d'abord 5*6, puis ajoute le résultat à -2. Pour choisir soi-même l'ordre dans lequel on effectue les opérations et calculer par exemple -2 + 5 avant de multiplier le résultat par 6, on utilise tout simplement des parenthèses : (-2 + 5) * 6 .

Exercice : Afficher le texte "Trois heures quinze minutes contiennent M minutes, ou S secondes", suivi d'un retour à la ligne, où M est remplacé par le nombre de minutes et S par le nombre de secondes. Ecrivez votre programme en utilisant uniquement les nombres 60, 15 et 3.


Solution :

 
#include 
 
int main()
{
   printf("Trois heures quinze minutes contiennent %d minutes, ", 3 * 60 + 15);
   printf("ou %d secondes\n", (3 * 60 + 15) * 60);
   return 0;
}

Il est possible d'afficher plusieurs nombres en une seule instruction printf. On met pour cela deux fois "%d" dans la même chaîne, puis on place après la chaîne, séparées par des virgules, les deux expressions dont la valeur doit être affichée, dans l'ordre dans lequel leur résultat doit remplacer les "%d". Si par exemple on veut afficher deux nombres, 42 et 54, on peut écrire l'instruction suivante :

   printf("Le premier nombre est %d, le deuxième nombre est %d\n", 42, 54);

Cette instruction affichera le texte suivant :

Le premier nombre est 42, le deuxième nombre est 54

On peut ainsi afficher autant de nombres qu'on le souhaite, en une seule instruction printf. Il ne faut cependant pas en abuser, pour ne pas rendre le programme difficile à lire. En général on se limitera à trois ou quatre nombres maximum dans un même printf.

Exercice : écrivez la solution de l'exercice précédent en n'utilisant qu'une instruction printf.


Solution :

 
#include 
 
int main()
{
   printf("Trois heures quinze minutes contiennent %d minutes, ou %d secondes\n",
      3 * 60 + 15, (3 * 60 + 15) * 60);
   return 0;
}

1.1.3 Erreurs de compilation

Si vous faites une erreur lors de l'écriture de votre programme, et que du fait de cette erreur, celui-ci n'est plus un programme C valide, un message d'erreur sera affiché au moment où vous compilerez votre programme. Ces messages sont affichés en anglais et il faut parfois un peu d'habitude pour comprendre ce qui les a provoqué : le compilateur ne vous indique en effet pas l'erreur exacte que vous avez commise, mais plutôt l'endroit où il l'a détectée. Nous allons donc étudier plusieurs exemples.

Chaque instruction C doit, pour être valide, se terminer par un caractère ";" (un point virgule), qui permet de séparer les différentes instructions. Par exemple, le programme suivant est invalide, car il manque un ";" après le premier printf. Compilez-le pour voir le résultat obtenu :

 
#include 
 
int main()
{
   printf("Hello ")
   printf("World !");
   return 0;
}

L'erreur affichée est la suivante :

test.c: In function `main':
test.c:6: parse error before `printf'

La première ligne de l'erreur indique que celle-ci se trouve dans la fonction nommée "main". Nous verrons plus tard en détail la notion de fonction, et vous comprendrez facilement ce message. C'est plutôt la deuxième ligne qui nous intéresse. Le message "parse error before printf" se traduit en français "erreur de syntaxe avant printf". Une erreur de syntaxe peut être considérée comme une erreur de grammaire : vous n'avez pas respecté les règles du langage C.

L'erreur est indiquée comme se trouvant sur la ligne 6, juste avant le "printf". C'est en effet à cette ligne que le compilateur détecte une erreur, car jusqu'à la ligne précédente incluse, tout est parfaitement valide. Le problème est que l'on ne peut pas ajouter un printf sur la ligne 6, sans avoir auparavant ajouté un ";". On peut par exemple corriger le problème en modifiant uniquement la ligne 6 :

 
#include 
 
int main()
{
   printf("Hello ")
   ;printf("World !");
   return 0;
}

Le langage C n'impose en effet aucune règle, quand à la position précise du ";", ni de la plupart des éléments. Le programme suivant est par exemple parfaitement équivalent au précédent :

#include 
 
int
main(
){printf
("Hello "
);printf
("World !"
);return
0;}

S'il est équivalent pour la machine, il est cependant beaucoup plus difficile à comprendre pour l'être humain, c'est pourquoi il est important de suivre un certain nombre de règles de présentation, pour avoir un code source le plus lisible possible. La première de ces règles peut être de ne toujours mettre qu'une seule instruction par ligne (et de mettre le ";" sur cette même ligne). Nous en verrons d'autres dans ce cours et surtout nous nous efforcerons de montrer l'exemple.


Cherchez maintenant l'erreur dans le programme suivant :

#include 
 
int main()
{
   printf("Hello);
   printf("World !");
   return 0;
}

Si vous compilez ce programme, vous obtiendrez l'erreur de compilation suivante :

none.c:6: unterminated string or character constant
none.c:5: possible real start of unterminated constant

Le message indique ici qu'une chaîne de caractères n'est pas terminée, à la ligne 6. La chaîne de caractères qui n'est pas terminée est ici notre "Hello", puisqu'il manque le guillemet de fin. Ce "Hello" se trouve cependant sur la ligne 5, mais le compilateur, une fois de plus, ne s'en rend compte que plus tard : il considère d'une part le texte entre guillemets :

"Hello);
   printf("

Et le texte entre guillemets qui n'est pour lui pas terminé est le suivant :

");
   return 0;
}

Le texte entre les deux, "World !" est considéré comme étant en dehors de toute chaîne de caractères.

La ligne d'erreur suivante montre cependant que le compilateur estime probable que l'erreur se trouve en fait à la ligne 5, où se trouve effectivement notre chaîne non terminée.

Lorsqu'un programme contient plusieurs erreurs, le compilateur les affiche toutes, les unes après les autres, pour vous permettre de toutes les corriger tout de suite. Il est cependant important de se concentrer surtout sur la première de ces erreurs. Les erreurs suivantes sont parfois de simples conséquences de la première et ne se produisent plus une fois cette première erreur corrigée.

De nombreux autres types d'erreurs peuvent être produits à partir de notre code source actuel et vous pouvez essayer vous-même d'en générer d'autres, pour voir le résultat. Un bon nombre d'entre-elles ne sera cependant compréhensible que lorsque nous aurons avancé un peu plus dans ce cours.

1.2 Variables

1.2.1 Déclarations et utilisations de variables

Considérons l'instruction C suivante, qui affiche le nombre 5, et son carré :

   printf("Le nombre %d a pour carré %d\n", 5, 5 * 5);

Supposons maintenant que nous souhaitions afficher le carré d'un autre nombre, par exemple 238. L'instruction devient alors :

   printf("Le nombre %d a pour carré %d\n", 238, 238 * 238);

Nous avons dû changer trois fois le nombre 5 en nombre 238. Imaginez que nous souhaitions ensuite afficher le carré du nombre 123458, il faudrait de nouveau taper ce nombre trois fois. Il est possible d'écrire notre programme différemment de telle sorte qu'on puisse ensuite changer ce nombre en une seule modification. Il faut pour cela utiliser une variable :

#include 
 
int main()
{
   int nombre;
   nombre = 238;
   printf("Le nombre %d a pour carré %d\n", nombre, nombre * nombre);
   return 0;
}

Dans cette nouvelle version, on a remplacé dans le printf la valeur 238 par le mot nombre. Les deux lignes qui précèdent font en sorte que l'utilisation de ce mot soit équivalente à l'utilisation de la valeur 238 :

  • int nombre;
    indique que le mot
    nombre va désormais représenter une valeur entière : int est l'abréviation du mot anglais "Integer", qui signifie nombre entier. Cette ligne est une déclaration de variable, c'est à dire que l'on déclare qu'un identifiant (ici le mot nombre) sera désormais associé à une valeur, ici un entier.
  • nombre = 238;
    indique que lors des instructions suivantes, la variable
    nombre aura pour valeur 238. Le nom variable signifie que la valeur que l'on associe à l'identifiant peut changer au cours du temps. Cette valeur n'est donc pas définitive et pourra être changée par d'autres instructions comme celle-ci.

Grâce à l'utilisation d'une variable, le nombre 238 n'apparaît plus qu'une seule fois et peut se changer en un seul remplacement.

L'instruction "nombre = 238;" est ce que l'on appelle une affectation. Le signe "=" indique que l'on va affecter la valeur à droite du signe, à la variable indiquée à sa gauche. Ce que l'on met à droite n'est pas nécessairement un simple entier : il peut par exemple s'agir d'une expression mathématique, qui doit être calculée, avant d'être affectée à la variable. Par exemple l'instruction "nombre = 238 + 42 * 42;" permet d'affecter à la variable "nombre" le résultat du calcul de l'expression "238 + 42 * 42", c'est-à-dire 2002.

On peut traduire l'instruction "int nombre;" en français comme ceci :

Définis que dans toutes les instructions qui suivent, "nombre" représente une variable entière.

Si l'on traduit en français "nombre = ... ;", cela donne :

  1. calcule la valeur de l’expression entre le signe = et le ';',
  2. remplace le mot "nombre" par cette valeur dans les instructions qui suivent,
  3. passe alors à l’exécution de la suite.

· On a utilisé le mot (l'identifiant) "nombre" pour y associer une valeur. On aurait très bien pu prendre comme nom n'importe quel mot satisfaisant à la règle suivante :

Un identifiant doit être formé de lettres minuscules ou majuscules (de a à z, ou de A à Z), de chiffres, et de caractères soulignés (le symbole "_", appelé underscore en anglais). Il ne doit en outre pas commencer par un chiffre, ni contenir d’espaces, de lettres accentuées ou tout autre caractère spécial.

· On ne peut pas placer de déclarations de variables n'importe où dans le programme :

Une déclaration de variable ne peut être précédée que par d'autres déclarations de variables. Un programme a donc la structure suivante :

  1. Les déclarations de variables.
  2. Les autres instructions du programme.

Exercice : parmi les propositions suivantes, lesquelles ne correspondent pas à des identifiants valides ?

  • hauteur
  • épaisseur
  • POIDS
  • niveau_d'eau
  • _Longueur_
  • longueur_plus_2
  • 2_fois_longueur

Solution : Les propositions suivantes ne sont pas des identifiants valides :

  • épaisseur (car il contient une lettre accentuée)
  • niveau_d'eau (car il contient une apostrophe)
  • 2_fois_longueur (car il commence par un chiffre)

Les autres propositions sont bien des identifiants.


Exercice : écrivez un programme qui pour deux valeurs entières 42 et 38 données, affiche leur somme, leur différence, puis leur produit. Les valeurs affichées devront être séparées par un espace.


Solution :

 
#include 
 
int main()
{
   int nombre1;
   int nombre2;
   nombre1 = 42;
   nombre2 = 38;
   printf("%d %d %d\n", nombre1 + nombre2, nombre1 - nombre2, nombre1 * nombre2);
   return 0;
}

1.2.2 Erreurs de compilation

Essayez de compiler le programme C suivant et trouvez l'erreur :

 
#include 
 
int main()
{
   int nombre1;
   nombre1 = 42;
   int nombre2;
   nombre2 = 38;
   printf("%d\n", nombre1 * nombre2);
   return 0;
}

En supposant que vous avez compilé le programme comme un programme C, et non C++, le compilateur affichera des messages d'erreurs dans ce genre :

test.c: In function `main':
test.c:7: parse error before `int'
test.c:8: `nombre2' undeclared (first use in this function)
test.c:8: (Each undeclared identifier is reported only once
test.c:8: for each function it appears in.)

Le message "parse error before 'int' " nous indique qu'une erreur de syntaxe est détectée au moment de la lecture du mot "int", sur la ligne 7. Ce mot clé du langage C n'a effectivement rien à faire à cet endroit, puisqu'il signifie que l'on souhaite faire une déclaration de variable, alors qu'on a déjà fait un autre type d'instruction sur la ligne précédente : une affectation.

Le message suivant indique en fait une autre erreur : le compilateur n'ayant pas réussi à compiler l'instruction invalide se trouvant à la ligne 7, il l'ignore et lit la suite, pour détecter d'éventuelles autres erreurs. "'nombre2' undeclared" indique que l'identifiant "nombre2" n'a pas été déclaré. En effet, la déclaration correspondante qui se trouvait à la ligne précédente était invalide et a été ignorée.

La suite du message est une remarque indiquant que c'est la première fois que cet identifiant est utilisé, et que le compilateur n'affichera pas d'erreur pour les autres utilisations.


1.2.3 Ecriture simplifiée

On peut remarquer qu'après une déclaration de variable, se trouve nécessairement une affectation d'une valeur à cette variable. En effet, si l'on déclare une variable sans l'initialiser, c'est-à-dire lui affecter ensuite une valeur, elle pourra contenir n'importe quoi. Afficher cette variable ou l'utiliser dans des calculs sans l'avoir initialisé donnera des résultats aléatoires, sans intérêt.

Plutôt que d'avoir à écrire systématiquement deux instructions, la déclaration d'une variable et son initialisation à une valeur :

   int nombre;
   nombre = 42;

Il est possible de n'écrire qu'une seule instruction :

   int nombre = 42;

Cette instruction simplifiée doit être considérée comme une déclaration, donc elle ne doit être précédée que par des déclarations et peut être suivie d'autres déclarations.


1.3 Lecture au clavier

1.3.1 Lecture d'un entier

Nous allons maintenant voir un cas où l'utilisation d'une variable est indispensable. Il s'agit de demander un entier à l'utilisateur lors de l'exécution du programme, et d'afficher sa valeur et celle de son carré. La requête d'un entier à l'utilisateur se fait avec la commande scanf et son utilisation est assez similaire à celle de printf. Voici un exemple de programme qui lit un entier au clavier puis affiche son carré :

 
#include 
 
int main()
{
   int nombre;
   printf("Entrez un entier : ");
   scanf("%d", &nombre);
   printf("Le carré de %d est %d\n", nombre, nombre * nombre);
   return 0;
}

Pour comprendre ce qui se passe lors de l'exécution du code ci-dessus :

L'instruction scanf("%d", &nombre); bloque l'exécution du programme jusqu'à ce que l'utilisateur ait tapé un nombre entier et ait appuyé sur la touche entrée du clavier. A ce moment, la valeur de l'entier tapé est stockée dans la variable nombre.

De la même manière que pour afficher un entier, il fallait utiliser la chaîne "%d" avec printf puis l'entier à afficher, on utilise la chaîne "%d" avec scanf pour lui indiquer qu'on veut lire un entier. Pour indiquer que l'on ne veut pas simplement remplacer l'identifiant nombre par sa valeur, lors de l'exécution de l'instruction scanf, mais que l'on fournit la variable pour que sa valeur puisse être modifiée, on place le caractère "&" devant l'identifiant.

Nous verrons plus tard la signification exacte de ce caractère "&" et considérerons pour l'instant qu'il permet simplement d'indiquer à scanf que la valeur lue au clavier peut être stockée dans la variable qui suit ce caractère.

Une fois que l'utilisateur a entré un nombre, puis appuyé sur la touche entrée, l'exécution du reste du code est identique à ce qui se serait produit si l'on avait écrit les instructions suivantes :

   nombre = 15;
   printf("Le carré de %d est %d\n", nombre, nombre * nombre);

Au final à l'écran, il sera affiché :

Entrez un entier : 15
15 a pour carré 225

Faites aussi un test avec un nombre entier négatif, par exemple -8. Vous devriez avoir au final :

Entrez un entier : -8
-8 a pour carré 64

Comme pour l'instruction printf, on peut placer plusieurs "%d" dans le texte fourni à scanf, pour lui indiquer que plusieurs entiers doivent être lus. L'instruction scanf("%d%d", &nombre1, &nombre2); permet ainsi de lire deux nombres au clavier. Pour que les nombres soient pris en compte, l'utilisateur devra séparer les nombres soit par un espace, soit par une tabulation, soit par un retour à la ligne. Il n'est cependant pas nécessaire de mettre d'espace ou de caractère "\n" entre, ou après les "%d" dans le scanf. C'est d'ailleurs tout à fait déconseillé et peut poser des problèmes.

Par exemple, le programme suivant demande deux nombres à l'utilisateur, puis les affiche :

 
#include 
 
int main()
{
   int nombre1;
   int nombre2;
   printf("Veuillez entrer deux nombres séparés par un espace : ");
   scanf("%d%d", &nombre1, &nombre2);
   printf("Les deux nombres que vous avez tapés sont %d et %d\n", nombre1, nombre2);
   return 0;
}

1.3.2 Exercices

Exercice : écrivez un programme qui affiche le texte "Veuillez entrer un nombre : ", qui attend ensuite que l'utilisateur entre un nombre entier, et qui affiche alors la ligne suivante : "Votre nombre est : ", suivi de la valeur du nombre.


Solution :

 
#include 
 
int main()
{
   int nombre;
   printf("Veuillez entrer un nombre : ");
   scanf("%d", &nombre);
   printf("Votre nombre est %d\n", nombre);
   return 0;
}

Exercice : écrivez un programme qui demande à l'utilisateur deux nombres, puis qui en affiche la somme.


Solution :

 
#include 
 
int main()
{
   int nombre1;
   int nombre2;
   printf("Veuillez entrer deux nombres : ");
   scanf("%d%d", &nombre1, &nombre2);
   printf("La somme de ces deux nombres est %d\n", nombre1 + nombre2);
   return 0;
}

Maintenant que vous avez compris comment fonctionnait l'affichage et l'entrée de texte, pour permettre la vérification automatique de vos programmes par notre système d'évaluation, nous ne vous demanderons plus d'afficher de texte décoratif. Ainsi, on écrira par exemple pour cet exercice :

 
#include 
 
int main()
{
   int nombre1;
   int nombre2;
   scanf("%d%d", &nombre1, &nombre2);
   printf("%d\n", nombre1 + nombre2);
   return 0;
}

Remarque : il est aussi possible de calculer le résultat du calcul de la somme avant de l'afficher :

 
#include 
 
int main()
{
   int nombre1;
   int nombre2;
   int somme;
   scanf("%d%d", &nombre1, &nombre2);
   somme = nombre1 + nombre2;
   printf("%d\n", somme);
   return 0;
}
 

1.4 Instructions conditionnelles

1.4.1 Le if / else

Les instructions que nous avons vues jusqu'à présent permettent de lire des entiers, faire des calculs sur ces entiers et afficher le résultat de ces calculs, ainsi que du texte. Un ordinateur n'est cependant pas une simple calculatrice et peut faire beaucoup plus que de simples calculs. En particulier, nous allons voir comment il est possible de lui faire prendre des décisions, grâce aux instructions conditionnelles, qui permettent de déterminer le comportement d'un programme en fonction du résultat d'un test.

Pour commencer, on va écrire un programme qui demande à l'utilisateur un nombre entier et qui affiche "positif" ou "négatif" en fonction du signe de ce nombre. Les mots du langage C que nous allons utiliser sont le if et le else, qui se traduisent en français "si" et "sinon".

Dans la description qui suit, on utilise le mot "bloc" pour signifier "un groupe d'instructions".

if (...la condition...)
   (...bloc du if...)
else
   (...bloc du else...)

Fonctionnement de la structure if, else :

  • Evaluer la condition qui se trouve après le if.
  • Si cette condition est vraie, on exécute le bloc du if, mais pas le bloc du else.
  • Dans le cas contraire, on exécute le bloc du else, mais pas le bloc du if.
  • Dans les deux cas, on exécute ensuite les instructions qui suivent le bloc du else.

Voyons comment écrire le programme qui affiche si un nombre entré par l'utilisateur est positif ou négatif :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre < 0)
   {
      printf("négatif");
   }
   else
   {
      printf("positif");
   }
   return 0;
}

Voici les éléments nécessaires pour utiliser une telle structure :

1. La condition peut s'exprimer sous la forme d'une comparaison de valeurs. Ici, la condition nombre <> est vraie si la valeur stockée dans la variable nombre est inférieure à 0. D'autres types de comparaisons sont possibles : "==" pour l'égalité, "!=" pour la différence, "<" et ">" pour les comparaisons strictes, "<=" et ">=" pour les comparaisons larges (inférieur ou égal / supérieur ou égal).

2. Afin de délimiter les blocs, chacun d'eux commence par le caractère "{" (accolade ouvrante) et se termine par le caractère "}" (accolade fermante). Entre ces deux accolades, on peut placer autant d'instructions qu'on le souhaite, y compris aucune.

1.4.2 Portée

Les déclarations réalisées jusqu'à maintenant consistaient à associer un nom à une valeur dans toute la suite du programme. Ce cas n'est pas général : il arrive qu'un nom soit associé à une valeur uniquement durant un petit morceau du programme. On dit alors que la "portée" de la déclaration est limitée.

On a vu plus tôt que les déclarations de variables devaient toujours se trouver avant tout autre type d'instruction. Il faut en fait être plus précis : une déclaration de variable doit toujours se trouver avant tout autre type d'instruction du bloc dans lequel elle se trouve.

Par exemple, le programme suivant, qui calcule la valeur absolue d'un nombre, est tout à fait correct :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre < 0)
   {
      int valeur_absolue;
      valeur_absolue = -nombre;
      printf("Votre nombre est négatif et a pour valeur absolue %d", valeur_absolue);
   }
   else
   {
      printf("Votre nombre est positif et a pour valeur absolue %d", nombre);
   }
   return 0;
}

La déclaration int valeur_absolue est en effet la toute première instruction du bloc du if et est donc valide, même s'il y a d'autres types d'instructions avant le début du bloc.

Regardez maintenant le code suivant, qui n'est pas valide :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre < 0)
   {
      int valeur_absolue;
      valeur_absolue = -nombre;
      printf("Votre nombre est négatif\n");
   }
   else
   {
      printf("Votre nombre est positif\n");
   }
   printf("La valeur absolue de votre nombre est %d\n", valeur_absolue);
   return 0;
}

Si ce code était autorisé, lorsque nombre est positif, seul le bloc du else serait exécuté, et la déclaration de la variable valeur_absolue ne serait alors jamais été exécutée. Que devrait-on alors afficher à la dernière ligne ? Vaudrait-il mieux afficher zéro, ne rien afficher, ou afficher un message d'erreur lors de l'exécution ? Aucune de ces possibilités n'est satisfaisante.

Afin d'éviter ce genre de problèmes, on interdit tout simplement d'utiliser des identifiants dont on n'est pas certain qu'ils aient été déclarés. Voici la règle à retenir :

Une déclaration effectuée dans un bloc a une portée limitée à ce bloc.

Ainsi le code suivant est tout à fait correct, mais après la première accolade fermante, l'identifiant "a" n'est plus associé à rien, et après la deuxième accolade fermante, l'identifiant "b" n'est plus associé à rien.

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre < 0)
   {
      int a = 5;
      printf("%d", a);
   }
   else
   {
      int b = 6;
      printf("%d", b);
   }
   return 0;
}

1.4.3 Exercices

Exercice : écrivez un programme qui lit un nombre au clavier puis affiche "égal à 4" si le nombre fourni par l'utilisateur est égal à 4, et "différent de 4" sinon. Dans les deux cas, affichez ensuite un retour à la ligne.


Solution :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre == 4)
   {
      printf("égal à 4\n");
   }
   else
   {
      printf("différent de 4\n");
   }
   return 0;
}

Exercice : écrivez un programme qui lit deux nombres au clavier et affiche le premier nombre, puis "et", le second nombre, puis "sont" et enfin "égaux" ou "différents" selon les cas.

Attention à bien mettre un espace entre chacun des éléments affichés. Par exemple si l'utilisateur tape les nombres 21 et 42, votre programme devra afficher précisément :

21 et 42 sont différents

Solution :

 
#include 
 
int main()
{
   int nombre1;
   int nombre2;
   scanf("%d%d", &nombre1, &nombre2);
   printf("%d et %d sont ", nombre1, nombre2);
   if (nombre1 == nombre2)
   {
      printf("égaux\n");
   }
   else
   {
      printf("différents\n");
   }
   return 0;
}

Exercice : écrivez un programme qui demande deux nombres à l'utilisateur et qui affiche la valeur du plus grand d'entre eux.


Solution :

 
#include 
 
int main()
{
   int nombre1;
   int nombre2;
   scanf("%d%d", &nombre1, &nombre2);
   if (nombre1 > nombre2)
   {
      printf("%d\n", nombre1);
   }
   else
   {
      printf("%d\n", nombre2);
   }
   return 0;
}

Remarque : on peut aussi faire le test nombre1 >= nombre2, vu que lorsque nombre1 est égal à nombre2, chacun des deux blocs affiche un résultat correct.


1.4.4 Réduction des blocs

Il est possible d'alléger les notations dans les structures conditionnelles (avec if) lorsque des blocs sont formés d'une seule instruction. Par exemple, le code suivant :

   scanf("%d%d", &nombre1, &nombre2);
   if (nombre1 > nombre2)
   {
      printf("%d\n", nombre1);
   }
   else
   {
      printf("%d\n", nombre2);
   }

Peut s'écrire de la manière suivante :

   scanf("%d%d", &nombre1, &nombre2);
   if (nombre1 > nombre2)
      printf("%d\n", nombre1);
   else
      printf("%d\n", nombre2);

Voici la règle à retenir :

Lorsqu'un bloc ne contient qu'une seule instruction, les accolades peuvent être retirées.

Il est important de noter que cette simplification ne fonctionne que si le bloc réduit ne contient qu'une instruction. Dès qu'il y a plusieurs instructions, les accolades sont obligatoires.

1.4.5 Blocs vides, else implicite

De même que pour un bloc ne contenant qu'une seule instruction, il est possible de simplifier un bloc ne contenant aucune instruction. Il suffit d'utiliser l'instruction vide, constituée uniquement d'un point virgule. Si par exemple on ne veut rien afficher si un nombre est égal à zéro, et afficher un message sinon, on peut écrire le code suivant :

if (nombre == 0)
{
}
else
   printf("Le nombre est différent de zéro");

On peut aussi le simplifier, et l'écrire de la manière suivante :

if (nombre == 0)
   ;
else
   printf("Le nombre est différent de zéro");

La règle à retenir est :

Lorsqu'un bloc ne contient aucune instruction, on peut le remplacer par un simple point virgule.

Attention : si on ne met pas le point virgule, le compilateur considèrera l'instruction qui suit comme faisant partie du bloc du if.

Pour toute condition, il est possible d'écrire la condition inverse. Le code précédent peut ainsi s'écrire :

if (nombre != 0)
   printf("Le nombre est différent de zéro");
else
   ;

Dans ce cas, il est cependant possible de simplifier encore plus l'écriture, en se passant tout simplement du bloc du else :

if (nombre != 0)
   printf("Le nombre est différent de zéro");

Exercice : écrivez un programme qui lit un entier et affiche "positif" si celui-ci est positif ou nul, et rien sinon.


Solution :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre >= 0)
      printf("positif");
   return 0;
}

Exercice : comment pourriez-vous simplifier le code suivant ?

 
#include 
 
int main()
{
   int nombre1, nombre2;
   printf("Entrez votre code secret numérique :\n");
   scanf("%d", &nombre1);
   printf("Confirmez votre code secret numérique : \n");
   scanf("%d", &nombre2);
   if (nombre1 == nombre2)
   {
   }
   else
   {
      printf("Erreur : les deux nombres sont différents\n");
   }
   return 0;
}

Indication : pensez à utiliser le test de différence, qui s'écrit "!=".


Solution : on peut dans ce cas utiliser la condition contraire en remplaçant "==" par "!=", ce qui permet de supprimer le bloc du else. On simplifie également le bloc ne contenant qu'une instruction.

 
#include 
 
int main()
{
   int nombre1, nombre2;
   printf("Entrez votre code secret numérique :\n");
   scanf("%d", &nombre1);
   printf("Confirmez votre code secret numérique : \n");
   scanf("%d", &nombre2);
   if (nombre1 != nombre2)
      printf("Erreur : les deux nombres sont différents\n");
   return 0;
}

1.4.6 Le else if

Exercice : écrivez un programme qui lit un nombre et affiche "c'est 4", ou bien "c'est 8", ou alors "c'est une autre valeur", selon l'entier fourni.

Indication : on peut placer un if à l'intérieur des accolades d'un bloc d'un autre if.


Solution :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre == 4)
      printf("c'est 4");
   else
   {
      if (nombre == 8)
         printf("c'est 8");
      else
         printf("c'est une autre valeur");
   }
   return 0;
}

Remarque : la solution n'est pas unique, on peut très bien aboutir à un résultat correct avec des tests différents.


Il est possible de simplifier le code précédent grâce à la règle suivante :

Lorsque le bloc du else contient lui-même une structure if, on peut enlever les accolades de ce bloc.

Observez ce que l'on obtient pour le code précédent et la manière dont on le présente :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre == 4)
      printf("c'est 4");
   else if (nombre == 8)
      printf("c'est 8");
   else
      printf("c'est une autre valeur");
   return 0;
}

Ce principe peut être généralisé, avec autant de "else if" qu'on le souhaite :

if (...1ère condition...)
   (...bloc du if...)
else if (...2ème condition...)
   (...bloc du 1er else if...)
else if (...3ème condition)
   (...bloc du 2ème else if...)
...
...
else
   (...bloc du else...)

Remarque : le dernier else peut être enlevé si le bloc associé est vide.

Fonctionnement de la structure if avec des else if :

  • Evaluer la condition du premier if.
  • Si celle-ci est vraie, on exécute le bloc juste après et on continue l'exécution après le dernier bloc de la structure.
  • Dans le cas contraire, aller jusqu'au else if qui suit et recommencer selon le même principe.
  • Si aucune des conditions de la structure n'est vraie, alors on exécute le bloc du else, s'il y en a un.

Remarque : si plusieurs des conditions de la structure sont vraies, seul le bloc de la première sera exécuté.

Exercice : écrivez un programme qui lit un nombre et affiche "strictement positif" si le nombre est positif et non nul, "strictement négatif" s'il est négatif et non nul, et "nul" s'il est égal à zéro.


Solution :

 
#include 
 
int main()
{
   int nombre;
   scanf("%d", &nombre);
   if (nombre > 0)
      printf("strictement positif");
   else if (nombre < 0)
      printf("strictement négatif");
   else
      printf("nul");
   return 0;
}

1.5 Répétitions

1.5.1 Répéter un bloc

L'un des avantages des ordinateurs est qu'ils sont capables d'effectuer des opérations un très grand nombre de fois, rapidement et sans fatiguer. Pour en profiter, il nous faut une structure permettant de répéter un morceau de programme plusieurs fois. Nous allons voir une première méthode pour le faire, la boucle while :

while (...condition du while...)
   (...bloc du while)

On remarque tout de suite que la structure while (qui signifie "tant que" en anglais) est similaire à celle du if. Son principe d'exécution a également des points communs, mais comporte une différence importante.

Fonctionnement d'une boucle while, utilisée pour répéter l'exécution d'un bloc :

  • On évalue la condition.
  • Si elle est fausse, on continue l'exécution après la fin du bloc.
  • Sinon, on exécute le bloc du while, puis on recommence.

La condition d'un while prend exactement la même forme que celle d'un if. Il en est de même pour le bloc. Voyons tout de suite un exemple : écrivons un programme qui demande à un utilisateur d'entrer un code secret (un entier). Si le code secret n'est pas celui attendu par le programme (456), le programme le lui redemande à nouveau, et répète l'opération jusqu'à-ce que le bon code soit entré par l'utilisateur.

On peut écrire le pseudo-code de notre idée. Le pseudo-code a pour but de décrire chaque étape en français, comme dans un programme, avant d'écrire le programme lui-même. On peut ainsi réfléchir à son principe, avant de se préoccuper des détails de l'écriture du programme.

Créer une variable qui contiendra le code entré par l'utilisateur.
Au départ, l'initialiser à 0.
Tant que le code entré par l'utilisateur n'est pas le code recherché (456) :
   - Demander à l'utilisateur de taper son code secret
   - stocker l'entier tapé par l'utilisateur dans notre variable
   - recommencer la boucle
Afficher un message indiquant que le code secret a été trouvé.

Voyons maintenant comment traduire ce programme en langage C :

 
#include 
 
int main()
{
   int code = 0;
   while (code != 456)
   {
      printf("Entrez le code secret numérique\n");
      scanf("%d", &code);
   }
   printf("Bravo, vous avez trouvé le code secret\n");
   return 0;
}

Ce programme demande à l'utilisateur d'entrer un code secret numérique et recommence inlassablement, tant que l'utilisateur n'entre pas le bon code secret : 456. Etudions d'un peu plus près son déroulement :

  • La variable code est déclarée et initialisée à la valeur 0.
  • La condition code != 456 est testée et est vraie, car 0 est différent de 456.
  • Le bloc du while est donc exécuté, en commençant par l'affichage du message demandant le code secret.
  • Le scanf attend ensuite l'entrée d'un entier, qui est stocké dans la variable code. Supposons que l'utilisateur tape 42.
  • La condition du while est de nouveau testée et est de nouveau vraie, car 42 est différent de 456.
  • Le bloc du while est de nouveau exécuté et un nouveau code secret est lu. Supposons cette fois que l'utilisateur tape 456.
  • La condition est testée une fois de plus, mais est cette fois fausse, puisque 456 n'est pas différent de lui-même.
  • Le bloc du while n'est pas exécuté et l'exécution se poursuit après la fin de celui-ci.
  • Le message de félicitations est alors affiché, et le programme se termine.

Comme dans le cas du if, on peut d'ailleurs simplifier l'écriture du bloc, s'il ne contient qu'une seule instruction. Par exemple si l'on ne souhaite afficher le texte "Entrez le code secret numérique" que la première fois, on écrira :

 
#include 
 
int main()
{
   int code = 0;
   printf("Entrez le code secret numérique\n");
   while (code != 456)
      scanf("%d", &code);
   printf("Bravo, vous avez trouvé le code secret\n");
   return 0;
}

Dans la description du déroulement du programme, on s'est arrêté à deux exécutions de la boucle (pour trois tests de la condition), mais on aurait pu en faire beaucoup plus, si l'utilisateur n'avait pas tapé le bon code. Si l'on ne fait pas attention, on peut obtenir un programme qui ne se terminera jamais, quoi que fasse l'utilisateur, comme c'est le cas du code ci-dessous :

 
while (0 + 0 == 0)
   printf("La tête à toto\n");
printf("Terminé\n");

La condition 0 + 0 == 0 est toujours vraie et le programme va donc afficher "La tête à toto\n" un nombre infini de fois et ne s'arrêtera jamais. Comme la seule instruction du bloc ne modifie rien qui puisse changer le fait que la condition soit vraie, cette condition ne sera jamais fausse, et les instructions qui suivent le bloc du while ne seront jamais exécutées. Le texte "Terminé" n'apparaîtra donc jamais, aussi longtemps que vous attendiez.

Exercice : écrivez un programme qui demande à l'utilisateur "Entrez votre code secret : " suivi d'un retour à la ligne, puis attend un entier, et affiche "Confirmez votre code secret : " (suivi d'un retour à la ligne) et attend un deuxième entier. Le programme doit répéter ces deux questions, jusqu'à-ce que que l'utilisateur entre bien deux fois le même nombre, puis afficher "merci".


Solution :

 
#include 
 
int main()
{
   int code = 0;
   int confirmation = 1;
   while (code != confirmation)
   {
      printf("Entrez votre code secret : \n");
      scanf("%d", &code);
      printf("Confirmez votre code secret : \n");
      scanf("%d", &confirmation);
   }
   printf("merci\n");
   return 0;
}

1.5.2 Répéter un nombre de fois donné

On a souvent besoin d'exécuter exactement la même opération un nombre bien précis de fois. On peut le faire avec une boucle while, en utilisant une variable, dans lequel on va stocker le nombre d'exécutions du bloc du while déjà effectuées. Il suffit alors de mettre comme condition du while, que le compteur doit être inférieur à une valeur donnée, pour que la boucle continue à s'exécuter.

Voyons par exemple le pseudo-code d'un programme qui affiche quatre fois "Hello World !".

initialiser le compteur à 0
tant que le compteur n'a pas atteint 4 :
 - afficher "Hello World !"
 - augmenter le compteur de 1

Vérifions que l'on affiche bien exactement quatre fois le mot "Hello World !" :

  • Le compteur vaut 0, donc on exécute la boucle.
  • On affiche "Hello World !", et on augmente le compteur de 1.
  • Le compteur vaut 1, donc on exécute le corps de la boucle.
  • On affiche "Hello World !", et on augmente le compteur de 1.
  • Le compteur vaut 2, donc on exécute le corps de la boucle.
  • On affiche "Hello World !", et on augmente le compteur de 1.
  • Le compteur vaut 3, donc on exécute le corps de la boucle.
  • On affiche "Hello World !", et on augmente le compteur de 1.
  • Le compteur vaut 4, donc on a atteint 4, on n'exécute pas le corps de la boucle.

On compte donc bien quatre affichages. Il est important de remarquer que l'on doit tester si le compteur vaut exactement 4. Si l'on testait s'il dépasse 4, on afficherait "Hello World !" une fois de trop.

Voyons maintenant comment transformer notre pseudo-code en programme C. Vous pouvez essayer par vous-même avant de lire la suite.

Pour commencer, nous devons choisir un identifiant pour notre compteur. On pourrait l'appeler simplement "compteur", mais ce n'est pas une très bonne idée, car c'est un peu vague et rendrait le programme difficile à comprendre. Mieux vaut par exemple choisir le nom plus explicite "nombre_affichages" qui permet de comprendre facilement que la variable contient le nombre de fois où le texte à été affiché. On peut choisir aussi "nb_affichages" qui est presque aussi clair, mais un peu plus court.

L'étape suivante consiste à déterminer ce que l'on met exactement comme condition. On doit exécuter le corps de la boucle si le compteur est plus petit que 4, mais pas s'il y est égal. On écrit donc (nb_affichages <>.

Il reste à trouver comment on augmente le compteur de 1. Il suffit pour cela d'écrire nb_affichages = nb_affichages + 1.

Voici le programme C correspondant :

 
#include 
 
int main()
{
   int nb_affichages = 0;
   while (nb_affichages < 4)
   {
      printf("Hello World!\n");
      nb_affichages = nb_affichages + 1;
   }
   return 0;
}

Exercice : écrivez un programme qui lit un entier au clavier et affiche le texte "bonjour" suivi d'un retour à la ligne, autant de fois que la valeur du nombre fourni par l'utilisateur.


Solution :

 
#include 
 
int main()
{
   int total_affichages;
   int nb_affichages = 0;
   scanf("%d", &total_affichages);
   while (nb_affichages < total_affichages)
   {
      printf("bonjour\n");
      nb_affichages = nb_affichages + 1;
   }
   return 0;
}

1.5.3 Règles générales sur les blocs

Comme pour le bloc du if ou du else, il est possible de déclarer des variables au début du bloc d'instructions du while, à l'intérieur des accolades. Ici aussi, les variables déclarées ne sont utilisables qu'à l'intérieur du bloc. Le même principe peut en fait s'appliquer à tout bloc d'instructions se trouvant à l'intérieur d'accolades :

Les déclarations de variables réalisées à l'intérieur du corps d'un bloc ont une portée limitée à ce bloc (entre les accolades).

De même, on peut généraliser les règles suivantes :

Un bloc d'instructions ne contenant qu'une instruction ne nécessite pas d'accolades.

Un bloc d'instructions ne contenant aucune instruction peut être remplacé par un point virgule.

Attention : vous avez sans doute remarqué que toutes nos instructions se trouvent elles-mêmes entre des accolades, entre la ligne qui suit le int main() et la dernière ligne. Les deux règles ci-dessus ne s'appliquent pas à ces accolades, comme nous le reverrons plus tard.

Il est possible de créer des blocs d'instructions à l'intérieur d'un programme, sans que ce soit dans le cadre d'un if ou d'un while, ni d'une autre structure. Il suffit pour cela de mettre plusieurs instructions entre accolades, à n'importe quel endroit où l'on aurait pu mettre une instruction.

Prenons par exemple le morceau de programme suivant :

   int nb_affichages = 0;
   while (nb_affichages < 3)
   {
      int nombre;
      scanf("%d", &nombre);
      printf("Votre nombre est %d\n", nombre);
      nb_affichages = nb_affichages + 1;
   }

On peut réécrire ce programme sans utiliser de boucle, mais en gardant les instructions du corps de la boucle dans un bloc d'instructions (ceci n'a pas d'autre intérêt que d'aider à comprendre la notion de bloc) :

   {
      int nombre;
      scanf("%d", &nombre);
      printf("Votre nombre est %d\n", nombre);
   }
   {
      int nombre;
      scanf("%d", &nombre);
      printf("Votre nombre est %d\n", nombre);
   }
   {
      int nombre;
      scanf("%d", &nombre);
      printf("Votre nombre est %d\n", nombre);
   }

Il faut bien comprendre qu'à l'intérieur de chaque bloc, une nouvelle variable est déclarée, indépendante de celles des autres blocs. La variable nombre n'existe plus dès que l'on sort d'un bloc, et n'est donc pas valide en dehors de ceux-ci. On en recrée une entièrement au début de chaque bloc.

1.5.4 Itérer un bloc

Répéter exactement la même chose ne sert en fait pas souvent. On a plutôt besoin de répéter presque la même chose, mais pour une valeur différente à chaque fois. Voyons un exemple :

   printf("%d\n", 5);
   printf("%d\n", 6);
   printf("%d\n", 7);
   printf("%d\n", 8);

On a quatre fois la même instruction, avec juste un nombre qui change. Il est cependant possible d'écrire le même programme en écrivant plusieurs fois exactement la même chose. Il faut pour cela utiliser une variable, qui contiendra la valeur à afficher, et que l'on augmente de 1 entre chaque affichage :

   int nombre_affiche = 5;
   printf("%d\n", nombre_affiche);
   nombre_affiche = nombre_affiche + 1;
   printf("%d\n", nombre_affiche);
   nombre_affiche = nombre_affiche + 1;
   printf("%d\n", nombre_affiche);
   nombre_affiche = nombre_affiche + 1;
   printf("%d\n", nombre_affiche);

On a ainsi obtenu maintenant trois fois exactement les mêmes deux instructions, et une fois de plus l'une des deux. Si l'on ajoute l'instruction nombre_affiche = nombre_affiche + 1; à la fin, on peut avoir quatre fois exactement la même chose, sans changer le fonctionnement du programme.

Maintenant qu'on a quatre fois la même chose, on peut utiliser une boucle qui répète ces deux instructions quatre fois, en utilisant ce que l'on a appris plus haut :

   int nombre_affiche = 5;
   int nb_affichages = 0;
   while (nb_affichages < 4)
   {
      printf("%d\n", nombre_affiche);
      nombre_affiche = nombre_affiche + 1;
      nb_affichages = nb_affichages + 1;
   }

On peut cependant remarquer que nos deux variables, nombre_affiche et nb_affichages sont toutes deux augmentées de 1 à chaque exécution (on dit itération) de la boucle. Il est en fait possible d'écrire la même chose avec une seule variable, en utilisant nombre_affiche comme compteur de la boucle. La condition doit cependant être transformée, car la boucle doit s'exécuter tant que le nombre affiché ne dépasse pas 8. Le programme complet est alors le suivant :

 
#include 
 
int main()
{
   int nombre_affiche = 5;
   while (nombre_affiche < 9)
   {
      printf("%d\n", nombre_affiche);
      nombre_affiche = nombre_affiche + 1;
   }
   return 0;
}

1.5.5 Exercices

Exercice : écrivez un programme qui affiche, pour chaque entier de 1 à 20, sa valeur, un espace, le texte "au carré vaut", un espace, la valeur de son carré et un retour à la ligne :

1 au carré vaut 1
2 au carré vaut 4
3 au carré vaut 9
...

Solution :

 
#include 
 
int main()
{
   int nombre = 1;
   while (nombre < 21)
   {
      printf("%d au carré vaut %d\n", nombre, nombre*nombre);
      nombre = nombre + 1;
   }
   return 0;
}

Exercice : écrivez un programme qui lit deux nombres au clavier et qui affiche dans l'ordre, un par ligne, tous les entiers compris entre ces deux nombres. On suppose que l'utilisateur entre le plus petit des deux nombres en premier.

Par exemple si l'utilisateur entre 211 puis 214, le programme devra afficher :

211
212
213
214

Solution :

 
#include 
 
int main()
{
   int nombre_actuel;
   int nombre_fin;
   scanf("%d%d", &nombre_actuel, &nombre_fin);
   while (nombre_actuel <= nombre_fin)
   {
      printf("%d\n", nombre_actuel);
      nombre_actuel = nombre_actuel + 1;
   }
   return 0;
}

Exercice : transformez votre programme de l'exercice précédent, pour que les nombres soient désormais affichés sur la même ligne, séparés par des virgules. Attention : il ne faut pas afficher de virgule après le dernier nombre, mais aller à la ligne.

Si l'utilisateur entre 42 puis 49, votre programme devra alors afficher :

42,43,44,45,46,47,48,49

Solution :

Dans cet exercice, on ne peut pas se contenter d'utiliser exactement les mêmes instructions un certain nombre de fois, puisqu'on doit afficher une virgule de moins que de nombres. Il y a deux manières de résoudre le problème :

· Afficher tous les nombres et la virgule qui les suit dans une boucle, sauf le dernier nombre, que l'on affiche en dehors de la boucle. Voici ce que cela donne (on a maintenant un inférieur strict "<" dans la condition) :

 
#include 
 
int main()
{
   int nombre_actuel;
   int nombre_fin;
   scanf("%d%d", &nombre_actuel, &nombre_fin);
   while (nombre_actuel < nombre_fin)
   {
      printf("%d,", nombre_actuel);
      nombre_actuel = nombre_actuel + 1;
   }
   printf("%d\n", nombre_actuel);
   return 0;
}

Remarque : lorsque l'on exécute la dernière instruction, on vient d'échouer le test de la condition du while pour la première fois, ce qui veut dire que nombre_actuel vaut nombre_fin. On peut donc afficher l'une ou l'autre variable.

· La deuxième solution consiste à afficher tous les nombres dans la boucle, mais à n'afficher la virgule que si l'on n'est pas rendu au dernier nombre. On affiche enfin le retour à la ligne une fois sorti de la boucle :

 
#include 
 
int main()
{
   int nombre_actuel;
   int nombre_fin;
   scanf("%d%d", &nombre_actuel, &nombre_fin);
   while (nombre_actuel <= nombre_fin)
   {
      printf("%d", nombre_actuel);
      if (nombre_actuel != nombre_fin)
         printf(",");
      nombre_actuel = nombre_actuel + 1;
   }
   printf("\n");
   return 0;
}

Parmi ces deux solutions, la première peut sembler un peu plus simple et plus courte. Cependant, dans le cas où l'on doit faire plus qu'afficher un nombre et une virgule à chaque itération de la boucle, mais que le corps de celle-ci contient plusieurs instructions, la première solution nécessite de recopier chacune de ces instructions après le corps de la boucle, pour l'exécution du dernier cas.

On préfèrera donc plutôt la deuxième solution, qui évite de recopier des instructions, ce qui est toujours une bonne chose.


Exercice : écrivez un programme qui lit deux nombres au clavier et qui affiche dans l'ordre inverse, séparés par des virgules, les entiers compris entre ces deux valeurs, en en sautant un sur deux. On suppose que l'utilisateur entre le plus petit des deux nombres en premier.

Par exemple, si l'utilisateur tape 11 puis 20, votre programme doit afficher :

20,18,16,14,12

Solution :

#include 
 
int main()
{
   int nombre_debut;
   int nombre_fin;
   int nombre_actuel;
   scanf("%d%d", &nombre_debut, &nombre_fin);
   nombre_actuel = nombre_fin;
   while (nombre_actuel >= nombre_debut)
   {
      if (nombre_actuel != nombre_fin)
         printf(",");
      printf("%d", nombre_actuel);
      nombre_actuel = nombre_actuel - 2;
   }
   return 0;
}
 

1.5.6 Opérateurs simplifiés

Cette section présente un certain nombre d'opérateurs permettant d'écrire plus simplement des instructions que l'on utilise assez souvent. Aucune de ces instructions ne permet de faire de nouvelles choses, ce sont simplement des manières plus courtes d'écrire certaines opérations.

Lors de l'écriture de boucles, on utilise souvent un compteur que l'on augmente de 1 à chaque itération de la boucle. Augmenter une variable de 1 s'appelle incrémenter cette variable. Cette opération est utilisée si fréquemment qu'une notation simplifiée existe dans le langage C :

   nombre_actuel = nombre_actuel + 1;

Cette instruction peut aussi s'écrire de la manière suivante, exactement équivalente, mais plus courte :

   nombre_actuel++;

De même, on peut souvent avoir besoin de faire l'opération inverse : retirer 1 à la valeur d'une variable, ce qui s'appelle décrémenter cette variable. On peut ainsi écrire, pour retirer 1 à nombre_actuel :

   nombre_actuel--;

Dans certaines boucles, on peut comme on l'a déjà vu en exercice, ajouter ou retirer une valeur à chaque itération, qui ne vaut pas forcément 1. On écrit par exemple :

   nombre_actuel = nombre_actuel - 2;

On ne peut bien sûr pas définir de raccourcis pour chaque valeur possible que l'on peut retirer une variable. On peut cependant éviter d'avoir à toujours écrire deux fois le nom de la variable à laquelle on veut ajouter ou retirer une valeur. Pour retirer 2 à la variable nombre_actuel, on peut ainsi écrire :

   nombre_actuel -= 2;

Le même type d'opérateurs existe pour toutes les opérations de base et d'autres que l'on verra plus tard. On peut par exemple écrire nombre_actuel *= 3 pour multiplier nombre_actuel par trois. On a également "+=" pour l'addition, et "/=" pour la division entière.

1.5.7 Erreurs

Essayez de compiler le source suivant :

 
#include 
 
int main()
{
   int nombre = 1;
   while (nombre < 21)
   {
      printf("%d au carré vaut %d\n", nombre, nombre*nombre);
      nombre = nombre + 1;
   printf("Le programme est terminé.\n");
   return 0;
}

Le compilateur vous donnera une erreur de type :

test.c: In function `main':
test.c:16: parse error at end of input

L'erreur peut se traduire en "Erreur de syntaxe à la fin de l'entrée". Cela signifie qu'une erreur de syntaxe est détectée à la fin de votre programme. Il faut comprendre par là que le compilateur trouve la fin du fichier, alors qu'il lui manque quelque-chose.

Ce qui manque ici, c'est l'accolade fermante correspondant à la première accolade ouvrante. En effet, comme on a oublié de fermer le bloc du while par une accolade, le compilateur considère que ce bloc se termine à la première accolade fermante qu'il trouve, c'est-à-dire juste avant la fin du fichier. Le bloc du while est alors considéré comme terminé, mais pas l'ensemble du programme, puisqu'il faudrait une deuxième accolade fermante.

Compilez et testez maintenant le programme suivant :

 
#include 
 
int main()
{
   int nombre = 1;
   while (nombre = 1)
   {
       printf("Le nombre vaut %d. Entrez une autre valeur.\n", nombre);
       scanf("%d", &nombre);
   }
   return 0;
}

Le programme vous demandera indéfiniment d'entrer d'autres valeurs, quel que soit le nombre que vous tapez. La raison est que la condition du while est fausse : pour tester si nombre vaut 1, il faut écrire (nombre == 1) et non (nombre = 1). Le signe "=" est en effet l'opérateur d'affectation, et la valeur 1 est donc stockée dans la variable nombre. La condition est dans ce cas toujours considérée comme vraie. Il faut donc bien faire attention à ne pas confondre "==" avec "=".


1.6 Propreté du code

Les quelques éléments du langages que vous avez pu découvrir dans ce chapitre vous permettent déjà d'écrire vos premiers programmes qui font quelque-chose d'utile. Vous avez sans doute déjà eu l'occasion de remarquer qu'il arrive souvent de commetre des erreurs lorsque vous écrivez un programme. Avec de l'expérience bien sûr, vous ne reproduirez plus certaines de ces erreurs. L'expérience ne suffit cependant pas, et tout programmeur commet des erreurs de temps en temps dans ses programmes.

Rechercher et corriger ses erreurs est une tâche difficile qui demande beaucoup de temps, souvent plus que l'écriture initiale du programme. Pour avoir un programme sans erreurs, le pire que l'on puisse faire est donc d'écrire son programme le plus vite possible, pour ensuite le tester et chercher les erreurs une par une. Comme on dit souvent, mieux vaut prévenir que guérir : il vaut mieux prendre son temps lorsque l'on écrit un programme et faire bien attention à tout ce que l'on écrit, plutôt que de l'écrire rapidement, pour corriger les erreurs lorsqu'elles apparaîtront.

Prendre son temps pour faire les choses bien est la base indispensable pour écrire des programmes avec peu d'erreurs et économiser le temps de leur recherche et correction. Cela ne suffit cependant pas et un certain nombre d'habitudes doivent être prises dans la manière dont vous programmez, pour réduire le nombre d'erreurs au minimum. Nous en verrons plusieurs tout au long de ce cours, en voici quelques unes :

  • Réfléchissez avant de coder.

Si vous vous précipitez sur le clavier dès que vous avez une idée de programme, vous avez toutes les chances de partir dans une mauvaise direction. Une fois que vous commencez à taper votre code, vous vous concentrez sur son écriture et perdez du recul sur l'ensemble de votre programme. Prenez plutôt une feuille de papier et un crayon et réfléchissez à la manière dont vous allez organiser vore programme. Prenez l'habitude d'écrire le pseudo-code de vos idées en français, pour les avoir bien en tête au moment où vous commencerez à programmer.

  • Faites simple.

Ceci est probablement la règle la plus importante en programmation : faire le plus simple possible. Ne soyez jamais fier d'avoir écrit des milliers de lignes de code. Pour un même résultat, un programme court et simple a beaucoup plus de valeur qu'un long programme qui fait la même chose. Prenez toujours le temps de chercher le moyen le plus simple d'écrire votre programme, vous en serez toujours récompensé. Plus un programme est simple, moins il contiendra d'erreurs et plus il sera facile de l'améliorer.

  • Utilisez des identifiants explicites.

Lorsque vous déclarez une variable, prenez le temps de rechercher l'identifiant le plus adapté à ce qu'elle va contenir. Une instruction comme "while (ligne <>" est bien plus facile à comprendre que si l'on avait écrit "while (toto <>". L'objectif est que votre programme se lise quasiment comme du français, et se comprenne sans effort. Si vous devez écrire une variable qui va servir de compteur de colonnes, n'appelez pas votre variable "compteur", qui est assez vague, mais plutôt "colonne".

  • Fixez-vous une convention d'écriture.

Le langage C vous laisse une grande liberté dans la manière dont vous présentez votre programme. Vous pouvez ajouter des espaces, tabulations, retours à la ligne n'importe où sauf au milieu d'un mot, sans que cela change son fonctionnement. Il est cependant important de prendre des habitudes et d'écrire ses programmes toujours de la même manière. Cela les rend plus faciles à relire et permet de voir les erreurs plus facilement.

Le dernier point demande un certain travail préalable, pour décider de votre propre convention. Nous ne vous imposons aucune convention, c'est à vous de faire ce qui vous convient le mieux. Pour vous y aider, nous allons détailler certains aspects de la convention que nous utilisons dans les programmes fournis dans ce cours. A vous de voir ce que vous souhaitez réutiliser :

  • Nommage des variables : nos variables sont nommées en français, sous la forme de mots parfois abrégés, écrits en lettres minuscules et séparés par des caractères soulignés "_". Le français a été choisi pour ce cours car il est destiné à un public francophone et jeune. En dehors de ce cours, nous utilisons plutôt l'anglais, pour pouvoir être lus par des non-francophones, entre autres raisons.
  • Une instruction par ligne : c'est une règle systématique qui facilite la lecture et réduit les risques d'erreurs. Une ligne contient une instruction et une seule et dans le cas d'une structure if, l'instruction exécutée si la condition est vraie se trouve sur la ligne qui suit le if. De même, les lignes contenant des accolades de début et de fin de bloc ne contiennent rien d'autre.
  • Indentation du code : un programme C est constitué de blocs imbriqués les uns dans les autres. Pour bien mettre en évidence cette structure, les instructions d'un bloc sont décalées d'un certain nombre de caractères vers la droite (3 dans notre cas), par rapport à celles du bloc qui le contient. Ceci permet de voir au premier coup d'oeil où commence et se termine chaque bloc d'instructions :
·                 
·                #include 
·                 
·                int main()
·                {
·                   int nombre = 0;
·                   printf("Instruction entre accolades, donc indentée de 3 caractères\n");
·                   while (nombre < 5)
·                   {
·                      printf("Instruction indentée de six caractères\n");
·                      if (nombre < 2)
·                      {
·                         printf("Celle-ci l'est encore plus (9)\n");
·                      }
·                      else
·                         printf("On indente, qu'il y ait ou non des accolades.\n");
·                      nombre++;
·                   }
·                   printf("Après un bloc, revient au niveau d'avant le bloc.\n");
·                   return 0;
·                }

De nombreux éditeurs de texte peuvent vous faciliter le travail de l'indentation. Vous pouvez par exemple utiliser la touche tabulation, pour ajouter un niveau d'indentation. Un conseil cependant : configurez votre éditeur pour qu'il ajoute des espaces et non un caractère tabulation. En effet, selon l'outil, une tabulation n'a pas toujours la même taille, et un source contenant des tabulations peut changer d'aspect suivant l'éditeur, et vous pouvez finir par avoir une indentation irrégulière.

  • Espaces entre les mots clés, identifiants et opérateurs : nous plaçons systématiquement un espace avant et après chaque opérateur, que ce soit un opérateur d'affectation (=, +=, etc.) ou un opérateur de calcul, ou de comparaison (+, *, <=, etc. ). Ceci permet de les mettre plus en évidence. On ne met par contre pas d'espace juste après une parenthèse ouvrante, ou juste avant une parenthèse fermante.

On met par contre un espace entre un mot clé et une parenthèse ouvrante, comme dans if (nombre <>. Il n'y en aura par contre pas dans le cas de l'utilisation de fonctions, comme pour printf("bonjour");. De plus, comme en français, on ne mettra pas d'espace avant une virgule, mais on en met un après.

Exercice : après avoir fait vos propres choix de convention d'écriture de programme, mettez-les en application sur le programme suivant, sans changer l'ordre des instructions, ni ce que fait le programme. Bien sûr, vous pouvez tout à fait adopter exactement la convention de ce cours si vous le souhaitez.

#include 
 
int main()
{
 int    Ma_Variable ;
printf   ("Bonjour, quel est votre âge ?" ) ; scanf ( "%d" , & Ma_Variable);
   if(Ma_Variable<>
 else { printf(
 "Vous êtes majeur\n");
 if( Ma_Variable<50>
printf (   "Vous avez moins de 50 ans\n"); else {
printf("Vous avez 50 ans ou plus\n" )
; printf ( "La moitié de votre âge est %d\n" ,Ma_Variable/ 2 ) ;
}
}
return 0;   
}

Solution : nous ne pouvons pas vous donner de solution absolue pour cet exercice, voici cependant comment nous appliquons la convention utilisée dans ce cours, pour écrire un tel programme :

 
#include 
 
int main()
{
   int age;
   printf("Bonjour, quel est votre âge ?");
   scanf("%d", &age);
   if(age < 18)
      printf("Vous êtes mineur\n");
   else
   {
      printf("Vous êtes majeur\n");
      if (age < 50)
         printf("Vous avez moins de 50 ans\n");
      else
      {
         printf("Vous avez 50 ans ou plus\n");
         printf("La moitié de votre âge est %d\n", age / 2);
      }
   }
   return 0;   
}
 

Commentaires

Enregistrer un commentaire